@bensandee/tooling 0.46.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -538,7 +538,7 @@ function createContext(config, confirmOverwrite) {
538
538
  remove: (rel) => {
539
539
  assertContained(config.targetDir, rel);
540
540
  const fullPath = path.join(config.targetDir, rel);
541
- if (existsSync(fullPath)) rmSync(fullPath);
541
+ if (existsSync(fullPath)) rmSync(fullPath, { recursive: true });
542
542
  },
543
543
  confirmOverwrite
544
544
  };
@@ -1129,13 +1129,14 @@ function normalizeWorkflow(content) {
1129
1129
  const DEV_BINARY_PATTERN = /node\s+packages\/tooling-cli\/dist\/bin\.mjs/;
1130
1130
  /**
1131
1131
  * If the existing file uses the dev binary path (`node packages/tooling-cli/dist/bin.mjs`),
1132
- * substitute it back into the generated content so we don't overwrite it with `pnpm exec bst`.
1132
+ * substitute it back into the generated content so we don't overwrite it with `pnpm exec bst`
1133
+ * or `pnpm dlx @bensandee/tooling`.
1133
1134
  */
1134
1135
  function preserveDevBinaryPath(generated, existing) {
1135
1136
  if (!existing) return generated;
1136
1137
  const match = DEV_BINARY_PATTERN.exec(existing);
1137
1138
  if (!match) return generated;
1138
- return generated.replaceAll("pnpm exec bst", match[0]);
1139
+ return generated.replaceAll("pnpm exec bst", match[0]).replaceAll("pnpm dlx @bensandee/tooling", match[0]);
1139
1140
  }
1140
1141
  /** Build a complete workflow YAML string from structured options. Single source of truth for workflow files. */
1141
1142
  function buildWorkflowYaml(options) {
@@ -1584,7 +1585,7 @@ async function generatePackageJson(ctx) {
1584
1585
  const devDeps = { ...ROOT_DEV_DEPS };
1585
1586
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1586
1587
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.1";
1587
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.46.0";
1588
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.48.0";
1588
1589
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
1589
1590
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
1590
1591
  "@changesets/cli": "2.30.0",
@@ -1653,6 +1654,16 @@ async function generatePackageJson(ctx) {
1653
1654
  changes.push(`updated devDependency: ${key} to ${value}`);
1654
1655
  }
1655
1656
  pkg.devDependencies = existingDevDeps;
1657
+ const existingDeps = pkg.dependencies ?? {};
1658
+ const commonKey = "@bensandee/common";
1659
+ if (commonKey in existingDeps && existingDeps[commonKey] !== "workspace:*") {
1660
+ const commonVersion = isWorkspacePackage(ctx, commonKey) ? "workspace:*" : "0.1.2";
1661
+ if (existingDeps[commonKey] !== commonVersion) {
1662
+ existingDeps[commonKey] = commonVersion;
1663
+ pkg.dependencies = existingDeps;
1664
+ changes.push(`updated dependency: ${commonKey} to ${commonVersion}`);
1665
+ }
1666
+ }
1656
1667
  if (!pkg.repository) {
1657
1668
  const repoUrl = detectGitRemoteUrl(ctx.targetDir);
1658
1669
  if (repoUrl) {
@@ -2192,11 +2203,7 @@ async function generateGitignore(ctx) {
2192
2203
  }
2193
2204
  //#endregion
2194
2205
  //#region src/generators/release-ci.ts
2195
- const DEFAULT_POST_RELEASE_COMMAND = [
2196
- "if [ -n \"$POST_RELEASE_HOOK_URL\" ]; then",
2197
- " pnpm dlx @bensandee/tooling webhook:send",
2198
- " \"$POST_RELEASE_HOOK_URL\"; fi"
2199
- ].join("");
2206
+ const DEFAULT_POST_RELEASE_COMMAND = ["pnpm exec bst webhook:send", " \"$POST_RELEASE_HOOK_URL\""].join("");
2200
2207
  /** Build env vars for hook secrets to inject into a workflow step. */
2201
2208
  function hookSecretEnv(secrets) {
2202
2209
  const env = {
@@ -2271,17 +2278,12 @@ function simpleWorkflowDispatchTrigger() {
2271
2278
  }
2272
2279
  } } };
2273
2280
  }
2274
- /** Build the release:simple run command with conditional flags from workflow inputs. */
2275
- function simpleReleaseCommand() {
2276
- return [
2277
- "FLAGS=",
2278
- `case "${actionsExpr("inputs.bump")}" in`,
2279
- " major|minor|patch) FLAGS=\"$FLAGS --release-as " + actionsExpr("inputs.bump") + "\" ;;",
2280
- " first-release) FLAGS=\"$FLAGS --first-release\" ;;",
2281
- "esac",
2282
- `if [ -n "${actionsExpr("inputs.prerelease")}" ]; then FLAGS="$FLAGS --prerelease ${actionsExpr("inputs.prerelease")}"; fi`,
2283
- "pnpm exec bst release:simple $FLAGS"
2284
- ].join("\n");
2281
+ /** Env vars that forward workflow dispatch inputs to the release:simple CLI. */
2282
+ function simpleReleaseEnv() {
2283
+ return {
2284
+ RELEASE_BUMP: actionsExpr("inputs.bump"),
2285
+ RELEASE_PRERELEASE: actionsExpr("inputs.prerelease")
2286
+ };
2285
2287
  }
2286
2288
  function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
2287
2289
  const releaseEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
@@ -2295,9 +2297,10 @@ function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, regist
2295
2297
  name: "Release",
2296
2298
  env: {
2297
2299
  ...releaseEnv,
2300
+ ...simpleReleaseEnv(),
2298
2301
  RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
2299
2302
  },
2300
- run: simpleReleaseCommand()
2303
+ run: "pnpm exec bst release:simple"
2301
2304
  } };
2302
2305
  const dockerStep = { step: {
2303
2306
  name: "Publish Docker images",
@@ -2404,7 +2407,6 @@ function changesetsReleaseStep(ci, publishesNpm) {
2404
2407
  return { step: {
2405
2408
  id: "release",
2406
2409
  name: "Release",
2407
- if: "github.ref == 'refs/heads/main'",
2408
2410
  env: {
2409
2411
  FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
2410
2412
  FORGEJO_REPOSITORY: actionsExpr("github.repository"),
@@ -2723,7 +2725,7 @@ function writeIfChanged(ctx, filePath, content, label) {
2723
2725
  };
2724
2726
  }
2725
2727
  async function generateClaudeHooks(ctx) {
2726
- return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.3\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport*as e from\"node:fs\";import{existsSync as t,readFileSync as n,readdirSync as r,statSync as i}from\"node:fs\";import*as a from\"node:path\";import{dirname as o,join as s,resolve as c}from\"node:path\";import{fileURLToPath as l}from\"node:url\";import{execFileSync as u}from\"node:child_process\";Object.freeze({status:`aborted`});function d(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;e<a.length;e++){let t=a[e];t in n||(n[t]=i[t].bind(n))}}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var f=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const p={};function m(e){return e&&Object.assign(p,e),p}function ee(e,t){return typeof t==`bigint`?t.toString():t}function te(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ne(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const re=Symbol(`evaluating`);function h(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==re)return r===void 0&&(r=re,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const ie=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function g(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ae(e){if(g(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(g(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function oe(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function _(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function se(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function v(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n<e.issues.length;n++)if(e.issues[n]?.continue!==!0)return!0;return!1}function y(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function b(e){return typeof e==`string`?e:e?.message}function x(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=b(e.inst?._zod.def?.error?.(e))??b(t?.error?.(e))??b(n.customError?.(e))??b(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const S=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,ee,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},ce=d(`$ZodError`,S),C=d(`$ZodError`,S,{Parent:Error}),le=(e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new f;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,m())));throw ie(t,i?.callee),t}return o.value})(C),ue=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,m())));throw ie(t,i?.callee),t}return o.value})(C),w=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new f;return a.issues.length?{success:!1,error:new(e??ce)(a.issues.map(e=>x(e,i,m())))}:{success:!0,data:a.value}})(C),T=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>x(e,i,m())))}:{success:!0,data:a.value}})(C),de=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},fe=/^-?\\d+(?:\\.\\d+)?$/,pe={major:4,minor:3,patch:6},E=d(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=pe;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=v(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new f;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=v(e,t))});else{if(e.issues.length===t)continue;r||=v(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(v(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(e=>t(e,r,a))}return t(o,r,a)}}h(e,`~standard`,()=>({validate:t=>{try{let n=w(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return T(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),me=d(`$ZodString`,(e,t)=>{E.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??de(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),he=d(`$ZodUnknown`,(e,t)=>{E.init(e,t),e._zod.parse=e=>e});function ge(e,t,n){e.issues.length&&t.issues.push(...y(n,e.issues)),t.value[n]=e.value}const _e=d(`$ZodArray`,(e,t)=>{E.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Array.isArray(i))return n.issues.push({expected:`array`,code:`invalid_type`,input:i,inst:e}),n;n.value=Array(i.length);let a=[];for(let e=0;e<i.length;e++){let o=i[e],s=t.element._zod.run({value:o,issues:[]},r);s instanceof Promise?a.push(s.then(t=>ge(t,n,e))):ge(s,n,e)}return a.length?Promise.all(a).then(()=>n):n}});function D(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...y(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function ve(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=se(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function ye(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>D(e,n,i,t,u))):D(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const be=d(`$ZodObject`,(e,t)=>{if(E.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=te(()=>ve(t));h(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=g,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>D(n,t,e,s,r))):D(i,t,e,s,r)}return i?ye(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),xe=d(`$ZodRecord`,(e,t)=>{E.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!ae(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...y(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...y(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&fe.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>x(e,r,m())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...y(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...y(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function Se(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const Ce=d(`$ZodOptional`,(e,t)=>{E.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,h(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),h(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ne(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>Se(t,e.value)):Se(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var we,Te=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function Ee(){return new Te}(we=globalThis).__zod_globalRegistry??(we.__zod_globalRegistry=Ee()),globalThis.__zod_globalRegistry;function De(e,t){return new e({type:`string`,..._(t)})}function Oe(e){return new e({type:`unknown`})}const O=d(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);E.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>le(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>w(e,t,n),e.parseAsync=async(t,n)=>ue(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>T(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>oe(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),ke=d(`ZodMiniString`,(e,t)=>{me.init(e,t),O.init(e,t)});function k(e){return De(ke,e)}const Ae=d(`ZodMiniUnknown`,(e,t)=>{he.init(e,t),O.init(e,t)});function A(){return Oe(Ae)}const je=d(`ZodMiniArray`,(e,t)=>{_e.init(e,t),O.init(e,t)});function Me(e,t){return new je({type:`array`,element:e,..._(t)})}const Ne=d(`ZodMiniObject`,(e,t)=>{be.init(e,t),O.init(e,t),h(e,`shape`,()=>t.shape)});function j(e,t){return new Ne({type:`object`,shape:e??{},..._(t)})}const Pe=d(`ZodMiniRecord`,(e,t)=>{xe.init(e,t),O.init(e,t)});function M(e,t,n){return new Pe({type:`record`,keyType:e,valueType:t,..._(n)})}const Fe=d(`ZodMiniOptional`,(e,t)=>{Ce.init(e,t),O.init(e,t)});function N(e){return new Fe({type:`optional`,innerType:e})}const Ie=j({session_id:k(),transcript_path:k(),cwd:k(),permission_mode:k(),hook_event_name:k(),tool_name:k(),tool_input:M(k(),A())});async function Le(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Ie.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Re=j({command:k(),description:N(k())}),ze=j({file_path:k()}),Be=j({pattern:k(),path:N(k())});function Ve(e){let t=Re.safeParse(e);return t.success?t.data:null}function He(e){let t=ze.safeParse(e);return t.success?t.data:null}function Ue(e){let t=Be.safeParse(e);return t.success?t.data:null}function P(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function We(){let e=a.dirname(l(import.meta.url));return a.join(e,`bst-hook.audit.local.jsonl`)}function Ge(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let Ke=!1;function qe(t){try{let n=We();Ke||=(Ge(n),!0);let r={timestamp:new Date().toISOString(),...t};e.appendFileSync(n,JSON.stringify(r)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function F(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function Je(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){I(t,n,`&&`),n=``,r+=2;continue}if(o===`&`&&!(r+1<e.length&&e[r+1]===`>`)&&!(r>0&&(e[r-1]===`>`||e[r-1]===`<`))){I(t,n,`&`),n=``,r++;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){I(t,n,`||`),n=``,r+=2;continue}if(o===`|`){I(t,n,`|`),n=``,r++;continue}if(o===`;`){I(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function I(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const L=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Ye(e,t,n=!1){if(/[\\n\\r]/.test(t))return{safe:!1,reason:`unquoted newline in command`};for(let t of e){if(t===\"`\")return{safe:!1,reason:`backtick command substitution`};if(t.includes(`$(`))return{safe:!1,reason:`dollar command substitution`};if(t.includes(\"`\"))return{safe:!1,reason:`backtick command substitution`};if(t.includes(`<(`)||t.includes(`>(`)&&!L.has(t))return{safe:!1,reason:`process substitution`}}if(!n)for(let t=0;t<e.length;t++){let n=e[t];if(!L.has(n)){if(n.includes(`<`)&&!n.includes(`<(`))return{safe:!1,reason:`input redirect`};if(n.includes(`>`)){if(n.endsWith(`>/dev/null`)||n.match(/^(&>>|&>|>>|>|1>|2>)$/)&&e[t+1]===`/dev/null`)continue;return{safe:!1,reason:`output redirect to file`}}}}return{safe:!0}}const Xe=[`.sample`,`.example`,`.template`],Ze=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function Qe(e){return Xe.some(t=>e.endsWith(t))}function R(e){return Qe(a.basename(e))?!1:Ze.test(e)}function $e(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&R(e))}const et=new Set([`status`,`log`,`diff`,`show`,`blame`,`grep`,`shortlog`,`range-diff`,`describe`,`branch`,`remote`,`fetch`,`ls-files`,`ls-tree`,`ls-remote`,`show-ref`,`for-each-ref`,`rev-parse`,`rev-list`,`cat-file`,`diff-tree`,`name-rev`,`merge-base`,`check-ignore`]),tt=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),nt=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),rt=new Set([`-C`,`-c`,`--git-dir`,`--work-tree`]);function z(e){let t=e.slice(1);for(let e=0;e<t.length;e++){let n=t[e];if(rt.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function it(e){for(let t of e.slice(1))if(nt.has(t)||t===`-o`||t.startsWith(`--output`)||t.startsWith(`--exec-path=`)||t.startsWith(`--pager=`)||t.startsWith(`--upload-pack=`)||t.startsWith(`--receive-pack=`)||t.startsWith(`-o=`)||t.startsWith(`--output=`))return!1;let t=z(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===`list`||t===void 0}return et.has(t)}function at(e){let t=z(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];if(t===`list`||t===void 0)return!1}return tt.has(t)}const ot=new Set([`push`,`pop`,`apply`,`show`]);function B(e){let t=z(e);if(!t)return!1;if(t===`add`)return!0;if(t===`checkout`)return e.slice(e.indexOf(`checkout`)+1).some(e=>e===`-b`||e===`-B`);if(t===`switch`)return!0;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===void 0?!0:ot.has(t)}return!1}const V=new Set(`install.i.add.remove.rm.uninstall.un.update.up.upgrade.list.ls.ll.la.why.view.info.show.outdated.audit.licenses.init.create.link.unlink.pack.publish.config.set.get.store.cache.rebuild.rb.prune.dedupe.dlx.exec.run.patch.patch-commit.recursive.-r`.split(`.`)),st=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function ct(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!V.has(e))){n=e;break}return n?st.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const lt=new Set([`view`,`info`,`show`]);function ut(e){let t=e[1];return t!==void 0&&lt.has(t)}function dt(e){let t=e[1],n=`This project uses pnpm.`;return t?t===`install`||t===`i`||t===`ci`?e.slice(2).some(e=>!e.startsWith(`-`))?`${n} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`:`${n} Use \\`pnpm install\\` instead of npm.`:t===`add`?`${n} Use \\`pnpm add <pkg>\\` instead of npm.`:t===`uninstall`||t===`remove`||t===`rm`||t===`un`||t===`r`?`${n} Use \\`pnpm remove <pkg>\\` instead of npm.`:t===`run`||t===`run-script`?`${n} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`:t===`test`||t===`t`||t===`tst`?`${n} Use \\`pnpm test\\` instead of npm.`:t===`start`?`${n} Use \\`pnpm start\\` instead of npm.`:t===`stop`?`${n} Use \\`pnpm stop\\` instead of npm.`:t===`restart`?`${n} Use \\`pnpm restart\\` instead of npm.`:t===`exec`?`${n} Use \\`pnpm exec <cmd>\\` instead of npm.`:t===`list`||t===`ls`||t===`ll`||t===`la`?`${n} Use \\`pnpm list\\` instead of npm.`:t===`why`?`${n} Use \\`pnpm why\\` instead of npm.`:t===`outdated`?`${n} Use \\`pnpm outdated\\` instead of npm.`:t===`audit`?`${n} Use \\`pnpm audit\\` instead of npm.`:t===`update`||t===`up`||t===`upgrade`?`${n} Use \\`pnpm update\\` instead of npm.`:t===`init`?`${n} Use \\`pnpm init\\` instead of npm.`:t===`publish`?`${n} Use \\`pnpm publish\\` instead of npm.`:t===`link`?`${n} Use \\`pnpm link\\` instead of npm.`:t===`unlink`?`${n} Use \\`pnpm unlink\\` instead of npm.`:t===`pack`?`${n} Use \\`pnpm pack\\` instead of npm.`:t===`cache`?`${n} Use \\`pnpm store\\` instead of \\`npm cache\\`.`:t===`config`||t===`set`||t===`get`?`${n} Use \\`pnpm config\\` instead of npm.`:`${n} Use the equivalent pnpm command instead of npm.`:`${n} Use \\`pnpm\\` instead of npm.`}function H(e,t){for(let n of e.slice(1)){if(n.startsWith(`-`)){if(n===`-t`||n.startsWith(`--target-directory`))return!1;continue}if(a.basename(n).startsWith(`.`)||n.startsWith(`~/`)||n===`~`)return!1;let e=a.resolve(t.cwd,n);if(e!==t.cwd&&!e.startsWith(t.cwd+a.sep))return!1}return!0}const ft=new Set([`-exec`,`-execdir`,`-ok`,`-okdir`,`-delete`,`-fprint`,`-fprint0`,`-fprintf`,`-fls`]);function pt(e){return!e.some(e=>ft.has(e))}function mt(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function ht(e){let t=e.findIndex(e=>a.basename(e)===`rm`);if(t===-1)return!1;let n=e.slice(t+1),r=n.filter(e=>e.startsWith(`-`)&&!e.startsWith(`--`)),i=n.filter(e=>e.startsWith(`--`)),o=r.some(e=>/[rR]/.test(e)),s=i.includes(`--recursive`);return o||s}function gt(e){return e.some(e=>e===`777`||e===`0777`)}function _t(e){let t=e.indexOf(`push`);if(t===-1)return!1;let n=e.slice(t+1);return n.some(e=>e===`--force`||e===`-f`&&!n.includes(`--force-with-lease`))}function vt(e){if(!(e.includes(`reset`)&&e.includes(`--hard`)))return!1;if(e.some(e=>e===`main`||e===`master`))return!0;let t=e.filter(e=>e!==`git`&&e!==`reset`&&!e.startsWith(`-`));return t.length===0||t.includes(`HEAD`)}const yt=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>ht(e),reason:\"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"},{name:`sudo`,match:/^sudo$/,action:`deny`,reason:`Don't use sudo. If you need elevated permissions, ask me to do it manually.`},{name:`git-force-push`,match:/^git$/,action:`deny`,when:e=>_t(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>vt(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>at(e)&&!B(e),reason:`Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.`},{name:`chmod-777`,match:/^chmod$/,action:`deny`,when:e=>gt(e),reason:`Don't use 777 permissions. Use more restrictive permissions.`},{name:`env-file-read`,match:/^(cat|head|tail|less|more|grep)$/,action:`deny`,when:e=>$e(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`npx-redirect`,match:/^npx$/,action:`deny`,reason:\"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"}],U=[{name:`vitest`,match:/^vitest$/,action:`allow`},{name:`jest`,match:/^jest$/,action:`allow`},{name:`tsc`,match:/^tsc$/,action:`allow`},{name:`tsdown`,match:/^tsdown$/,action:`allow`},{name:`oxlint`,match:/^oxlint$/,action:`allow`},{name:`oxfmt`,match:/^oxfmt$/,action:`allow`},{name:`eslint`,match:/^eslint$/,action:`allow`},{name:`prettier`,match:/^prettier$/,action:`allow`},{name:`biome`,match:/^biome$/,action:`allow`},{name:`knip`,match:/^knip$/,action:`allow`},{name:`cat`,match:/^cat$/,action:`allow`},{name:`head`,match:/^head$/,action:`allow`},{name:`tail`,match:/^tail$/,action:`allow`},{name:`grep`,match:/^grep$/,action:`allow`},{name:`ls`,match:/^ls$/,action:`allow`},{name:`wc`,match:/^wc$/,action:`allow`},{name:`uniq`,match:/^uniq$/,action:`allow`},{name:`diff`,match:/^diff$/,action:`allow`},{name:`echo`,match:/^echo$/,action:`allow`},{name:`pwd`,match:/^pwd$/,action:`allow`},{name:`which`,match:/^which$/,action:`allow`},{name:`dirname`,match:/^dirname$/,action:`allow`},{name:`basename`,match:/^basename$/,action:`allow`},{name:`realpath`,match:/^realpath$/,action:`allow`},{name:`npm`,match:/^npm$/,action:`allow`,when:e=>ut(e)},{name:`test`,match:/^test$/,action:`allow`},{name:`bracket-test`,match:/^\\[$/,action:`allow`},{name:`cd`,match:/^cd$/,action:`allow`},{name:`true`,match:/^true$/,action:`allow`},{name:`colon`,match:/^:$/,action:`allow`},{name:`find`,match:/^find$/,action:`allow`,when:e=>pt(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>mt(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>it(e)},{name:`git-safe-write`,match:/^git$/,action:`allow`,when:e=>B(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>ct(e)},{name:`cp`,match:/^cp$/,action:`allow`,when:(e,t)=>H(e,t)},{name:`mkdir`,match:/^mkdir$/,action:`allow`,when:(e,t)=>H(e,t)},{name:`touch`,match:/^touch$/,action:`allow`,when:(e,t)=>H(e,t)}],W=`.claude/hooks/bst-hook.settings.json`,bt=j({allow:Me(k())});function xt(e,t){try{return u(`git`,[`ls-files`,`--error-unmatch`,t],{cwd:e,stdio:`ignore`}),u(`git`,[`diff`,`--quiet`,`HEAD`,`--`,t],{cwd:e,stdio:`ignore`}),!0}catch{return!1}}function St(e){let r=Dt(e)??e,i=s(r,W);if(!t(i)||!xt(r,W))return[];try{let e=n(i,`utf-8`),t=JSON.parse(e),r=bt.safeParse(t);return r.success?r.data.allow.filter(e=>typeof e==`string`&&e.length>0):[]}catch{return[]}}function Ct(e,t,n){let r=n.split(/\\s+/).filter(Boolean);if(r.length===0||r[0]!==e)return!1;let i=r.slice(1),a=t.slice(1);for(let e=0;e<i.length;e++)if(e>=a.length||a[e]!==i[e])return!1;return!0}const wt=j({scripts:M(k(),A())});function G(e,t){let n=Nt(t);if(!n)return null;let r=n[e];if(typeof r!=`string`)return null;let i=n[`pre${e}`],a=n[`post${e}`];return{pre:typeof i==`string`?i:null,main:r,post:typeof a==`string`?a:null}}function Tt(e,t,n){let r=kt(t,n);if(!r)return null;let i=G(e,r);return i?{...i,pkgDir:r}:null}function Et(e,t){let n=Ot(t);if(!n)return null;let r=[];for(let t of n){let n=G(e,t);n&&r.push({...n,pkgDir:t})}return r}function Dt(e){let n=c(e);for(;;){if(t(s(n,`pnpm-workspace.yaml`)))return n;let e=o(n);if(e===n)return null;n=e}}function Ot(e){let t=At(e);if(!t)return null;let n=[];for(let a of t){if(!a.endsWith(`/*`))continue;let t=c(e,a.slice(0,-2)),o;try{o=r(t)}catch{continue}for(let e of o){let r=s(t,e);try{if(!i(r).isDirectory())continue}catch{continue}n.push(r)}}return n.length>0?n:null}function kt(e,t){let n=Ot(t);if(!n)return null;for(let t of n){let n=Mt(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function At(e){try{let t=n(s(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),r=t.findIndex(e=>/^packages\\s*:/.test(e));if(r===-1)return null;let i=[];for(let e=r+1;e<t.length;e++){let n=t[e],r=/^\\s+-\\s+(.+)$/.exec(n);if(!r)break;i.push(r[1].trim().replace(/['\"`]/g,``))}return i.length>0?i:null}catch{return null}}const jt=j({name:k()});function Mt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=jt.safeParse(JSON.parse(t));return r.success?r.data.name:null}catch{return null}}function Nt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=wt.safeParse(JSON.parse(t));return r.success?r.data.scripts:null}catch{return null}}function Pt(e){return xt(e,`package.json`)}const Ft=new Set([`LD_PRELOAD`,`LD_LIBRARY_PATH`,`DYLD_INSERT_LIBRARIES`,`NODE_OPTIONS`,`PYTHONPATH`,`PYTHONSTARTUP`,`PERL5OPT`,`PERL5LIB`,`RUBYOPT`,`RUBYLIB`,`BASH_ENV`,`ENV`,`npm_config_prefix`,`npm_config_registry`,`EDITOR`,`VISUAL`]),It=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function Lt(e){return Ft.has(e)?!0:It.some(t=>e.startsWith(t))}function Rt(e,t){let n=F(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let i=r[1];return a.isAbsolute(i)?i:a.resolve(t.cwd,i)}function K(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=Je(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=Rt(e.command,o);t&&(o={...o,cwd:t})}let t=zt(e.command,o);if(t.action===`deny`)return t;t.action===`passthrough`&&(i||=t),t.action===`allow`&&(a=t)}return i||(r.length===1&&a?a:{action:`allow`,rule:`all-segments`,reason:null})}function q(e,t){let n=e.pkgDir??t.cwd,r=t.insideResolvedScript??Pt(n)?{...t,insideResolvedScript:!0}:t;if(e.pre){let t=K(e.pre,r);if(t.action!==`allow`)return t}let i=K(e.main,r);if(i.action!==`allow`)return i;if(e.post){let t=K(e.post,r);if(t.action!==`allow`)return t}return`allow`}function zt(e,t){let n=e.trim();if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=F(e);if(!r.ok)return{action:`passthrough`,rule:null,reason:r.error};let i=r.tokens;if(i.length===0)return{action:`passthrough`,rule:null,reason:null};let o=0;for(;o<i.length&&/^\\w+=/.test(i[o]);){let e=i[o].split(`=`)[0];if(Lt(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};o++}if(o>=i.length)return{action:`allow`,rule:`env-only`,reason:null};i=i.slice(o);let s=Ye(i,e,t.insideResolvedScript);if(!s.safe)return{action:`passthrough`,rule:null,reason:s.reason};if(!t.insideResolvedScript)for(let e=0;e<i.length;e++){let t=i[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(i[e+1]===`/dev/null`)continue;return{action:`passthrough`,rule:null,reason:`output redirect to file`}}if(t===`<`)return{action:`passthrough`,rule:null,reason:`input redirect`}}}let c=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);i=i.filter((e,t)=>!(c.has(e)||(e===`>`||e===`>>`)&&i[t+1]===`/dev/null`||e===`/dev/null`&&t>0&&(i[t-1]===`>`||i[t-1]===`>>`)));let l=a.basename(i[0]);if(l===`command`&&i.length>1)return K(i.slice(1).join(` `),{...t,depth:t.depth+1});if(l===`env`){let e=1;for(;e<i.length&&/^\\w+=/.test(i[e]);){let t=i[e].split(`=`)[0];if(Lt(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=i.length?{action:`allow`,rule:`env-only`,reason:null}:K(i.slice(e).join(` `),{...t,depth:t.depth+1})}if(l===`pnpm`)return Bt(i,t);if(l===`npm`)return Ht(i);if(l===`bash`||l===`sh`||l===`zsh`)return Gt(i,t);if(l===`xargs`)return qt(i,t);if(l===`node`&&(i.includes(`-e`)||i.includes(`-p`)||i.includes(`--eval`)||i.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(l===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let e of yt)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`deny`,rule:e.name,reason:e.reason??null};for(let e of U)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`allow`,rule:e.name,reason:null};if(t.repoAllowPatterns){for(let e of t.repoAllowPatterns)if(Ct(l,i,e))return{action:`allow`,rule:`repo:${l}`,reason:null}}return t.insideResolvedScript?{action:`allow`,rule:`resolved-script`,reason:null}:{action:`passthrough`,rule:null,reason:`no rule matched: ${l}`}}function Bt(e,t){let n=e.slice(1),r,i=-1,o=!1,s=!1,c;for(let a=0;a<n.length;a++){let l=n[a];if(l===`--filter`||l.startsWith(`--filter=`))return Ut(e,n,a,t);if(l===`-r`||l===`--recursive`){o=!0;continue}if(l===`-w`||l===`--workspace-root`){s=!0;continue}if(l===`-C`||l===`--dir`){c=n[++a];continue}if(l.startsWith(`-C=`)||l.startsWith(`--dir=`)){c=l.split(`=`,2)[1];continue}if(!(l.startsWith(`-`)&&!V.has(l))){r=l,i=a;break}}if(!r)return{action:`passthrough`,rule:null,reason:null};if(r===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(r===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(r===`exec`){let e=n.slice(i+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:K(e.join(` `),{...t,depth:t.depth+1})}let l;if(r===`run`){let e=n[i+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};l=e}else if(!V.has(r))l=r;else{for(let n of U)if(n.match.test(`pnpm`)&&(!n.when||n.when(e,t)))return{action:`allow`,rule:n.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}if(o)return Vt(l,t);if(s){let e=Dt(t.cwd);return e?J(l,{...t,cwd:e}):{action:`passthrough`,rule:null,reason:`workspace root not found`}}if(c){let e=a.resolve(t.cwd,c);return J(l,{...t,cwd:e})}return J(l,t)}function J(e,t){let n=G(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r=q(n,{...t,depth:t.depth+1});return r===`allow`?{action:`allow`,rule:`pnpm-script:${e}`,reason:null}:r}function Vt(e,t){let n=Et(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n=q(e,{...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd});if(n!==`allow`)return n}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function Ht(e){for(let t of U)if(t.match.test(`npm`)&&(!t.when||t.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:t.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:dt(e)}}function Ut(e,t,n,r){let i=t[n],a,o;if(i.startsWith(`--filter=`))a=i.slice(9),o=n+1;else{if(a=t[n+1],!a||a.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};o=n+2}if(!a)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let s;for(let e=o;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!V.has(n))){s=n;break}}if(!s)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(s===`install`||s===`i`)return t.slice(t.indexOf(s)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(s===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(s===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(s))return{action:`allow`,rule:`pnpm`,reason:null};if(s===`exec`){let e=t.indexOf(`exec`,o),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:K(n.join(` `),{...r,depth:r.depth+1})}let c=s===`run`?t[t.indexOf(`run`,o)+1]:s;if(!c||c.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(V.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=Tt(c,a,r.cwd);if(!l)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let u=q(l,{...r,depth:r.depth+1,cwd:l.pkgDir??r.cwd});return u===`allow`?{action:`allow`,rule:`pnpm-filter:${a}:${c}`,reason:null}:u}const Wt=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function Gt(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||Wt.has(r))return{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}}let r=e[n+1];return r?e.length>n+2?{action:`passthrough`,rule:null,reason:`shell -c with positional args`}:n===1?K(r,{...t,depth:t.depth+1}):{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}:{action:`passthrough`,rule:null,reason:`shell -c without command`}}const Y=new Set([`-I`,`-d`,`-n`,`-P`,`-s`,`-L`,`-E`,`-a`,`--max-args`,`--max-procs`,`--delimiter`,`--replace`,`--max-lines`,`--max-chars`,`--arg-file`,`--process-slot-var`]),Kt=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function qt(e,t){let n=1;for(;n<e.length;){let t=e[n];if(!t.startsWith(`-`))break;if(Y.has(t)){n+=2;continue}let r=t.indexOf(`=`);if(r!==-1){let e=t.slice(0,r);if(Y.has(e)){n++;continue}}if(t.length>2&&Y.has(t.slice(0,2))){n++;continue}if(Kt.has(t)){n++;continue}return{action:`passthrough`,rule:null,reason:`xargs with unrecognised flags`}}return n>=e.length?{action:`passthrough`,rule:null,reason:`xargs without command`}:K(e.slice(n).join(` `),{...t,depth:t.depth+1})}const Jt=new Set([`Read`,`Glob`,`Grep`]),X=new Set([`Write`,`Edit`]),Yt=[{name:`env-file-read`,tools:Jt,action:`deny`,when:e=>R(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-write`,tools:X,action:`deny`,when:e=>R(e),reason:`Don't write to .env files. These contain secrets.`},{name:`hooks-folder-write`,tools:X,action:`deny`,when:(e,t)=>{let n=a.resolve(t.cwd,e),r=a.join(t.cwd,`.claude`,`hooks`);return n.startsWith(r+a.sep)||n===r},reason:`Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.`},{name:`claude-settings-write`,tools:X,action:`deny`,when:(e,t)=>a.resolve(t.cwd,e)===a.join(t.cwd,`.claude`,`settings.json`),reason:`Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.`},{name:`outside-cwd-write`,tools:X,action:`deny`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n!==t.cwd&&!n.startsWith(t.cwd+a.sep)},reason:`Don't write files outside the project directory.`}],Xt=[{name:`cwd-read`,tools:Jt,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}},{name:`cwd-write`,tools:X,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}}],Zt=new Set([`Read`,`Write`,`Edit`]),Qt=new Set([`Glob`,`Grep`]);function $t(e,t,n){return Zt.has(e)?He(t)?.file_path??null:Qt.has(e)?Ue(t)?.path??n:null}function en(e,t,n){let r=$t(e,t,n.cwd);if(r===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of Yt)if(t.tools.has(e)&&t.when(r,n))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of Xt)if(t.tools.has(e)&&t.when(r,n))return{action:`allow`,rule:t.name,reason:null};return{action:`passthrough`,rule:null,reason:`no file rule matched`}}const Z=await Le();Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);const tn=St(Z.cwd);let Q,$;if(Z.tool_name===`Bash`){let e=Ve(Z.tool_input);e||process.exit(0),Q=K(e.command,{cwd:Z.cwd,depth:0,repoAllowPatterns:tn}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=en(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}qe({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?P(`allow`):Q.action===`deny`&&P(`deny`,Q.reason);export{};", "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\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-write**: Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\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- **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- **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 safe writes**: git-safe-write (add, switch -c, checkout -b, stash push/pop/apply/show)\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)\n\n### File tools\n\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` (file moves)\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")];
2728
+ return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.5\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport*as e from\"node:fs\";import{existsSync as t,readFileSync as n,readdirSync as r,statSync as i}from\"node:fs\";import*as a from\"node:path\";import{dirname as o,join as s,resolve as c}from\"node:path\";import{fileURLToPath as l}from\"node:url\";import{execFileSync as u}from\"node:child_process\";Object.freeze({status:`aborted`});function d(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;e<a.length;e++){let t=a[e];t in n||(n[t]=i[t].bind(n))}}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var f=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const ee={};function p(e){return e&&Object.assign(ee,e),ee}function te(e,t){return typeof t==`bigint`?t.toString():t}function ne(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function re(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const ie=Symbol(`evaluating`);function m(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==ie)return r===void 0&&(r=ie,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const ae=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function h(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function oe(e){if(h(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(h(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function se(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function g(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function ce(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function _(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n<e.issues.length;n++)if(e.issues[n]?.continue!==!0)return!0;return!1}function v(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function y(e){return typeof e==`string`?e:e?.message}function b(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=y(e.inst?._zod.def?.error?.(e))??y(t?.error?.(e))??y(n.customError?.(e))??y(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const le=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,te,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},ue=d(`$ZodError`,le),x=d(`$ZodError`,le,{Parent:Error}),de=(e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new f;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>b(e,a,p())));throw ae(t,i?.callee),t}return o.value})(x),fe=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>b(e,a,p())));throw ae(t,i?.callee),t}return o.value})(x),S=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new f;return a.issues.length?{success:!1,error:new(e??ue)(a.issues.map(e=>b(e,i,p())))}:{success:!0,data:a.value}})(x),pe=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>b(e,i,p())))}:{success:!0,data:a.value}})(x),me=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},he=/^-?\\d+(?:\\.\\d+)?$/,ge={major:4,minor:3,patch:6},C=d(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=ge;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=_(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new f;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=_(e,t))});else{if(e.issues.length===t)continue;r||=_(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(_(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(e=>t(e,r,a))}return t(o,r,a)}}m(e,`~standard`,()=>({validate:t=>{try{let n=S(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return pe(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),_e=d(`$ZodString`,(e,t)=>{C.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??me(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),ve=d(`$ZodUnknown`,(e,t)=>{C.init(e,t),e._zod.parse=e=>e});function ye(e,t,n){e.issues.length&&t.issues.push(...v(n,e.issues)),t.value[n]=e.value}const be=d(`$ZodArray`,(e,t)=>{C.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Array.isArray(i))return n.issues.push({expected:`array`,code:`invalid_type`,input:i,inst:e}),n;n.value=Array(i.length);let a=[];for(let e=0;e<i.length;e++){let o=i[e],s=t.element._zod.run({value:o,issues:[]},r);s instanceof Promise?a.push(s.then(t=>ye(t,n,e))):ye(s,n,e)}return a.length?Promise.all(a).then(()=>n):n}});function w(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...v(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function xe(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=ce(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function Se(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>w(e,n,i,t,u))):w(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const Ce=d(`$ZodObject`,(e,t)=>{if(C.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=ne(()=>xe(t));m(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=h,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>w(n,t,e,s,r))):w(i,t,e,s,r)}return i?Se(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),we=d(`$ZodRecord`,(e,t)=>{C.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!oe(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...v(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...v(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&he.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>b(e,r,p())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...v(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...v(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function T(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const Te=d(`$ZodOptional`,(e,t)=>{C.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,m(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),m(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${re(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>T(t,e.value)):T(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var E,Ee=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function De(){return new Ee}(E=globalThis).__zod_globalRegistry??(E.__zod_globalRegistry=De()),globalThis.__zod_globalRegistry;function Oe(e,t){return new e({type:`string`,...g(t)})}function ke(e){return new e({type:`unknown`})}const D=d(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);C.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>de(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>S(e,t,n),e.parseAsync=async(t,n)=>fe(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>pe(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>se(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),Ae=d(`ZodMiniString`,(e,t)=>{_e.init(e,t),D.init(e,t)});function O(e){return Oe(Ae,e)}const je=d(`ZodMiniUnknown`,(e,t)=>{ve.init(e,t),D.init(e,t)});function Me(){return ke(je)}const Ne=d(`ZodMiniArray`,(e,t)=>{be.init(e,t),D.init(e,t)});function Pe(e,t){return new Ne({type:`array`,element:e,...g(t)})}const Fe=d(`ZodMiniObject`,(e,t)=>{Ce.init(e,t),D.init(e,t),m(e,`shape`,()=>t.shape)});function k(e,t){return new Fe({type:`object`,shape:e??{},...g(t)})}const Ie=d(`ZodMiniRecord`,(e,t)=>{we.init(e,t),D.init(e,t)});function A(e,t,n){return new Ie({type:`record`,keyType:e,valueType:t,...g(n)})}const Le=d(`ZodMiniOptional`,(e,t)=>{Te.init(e,t),D.init(e,t)});function j(e){return new Le({type:`optional`,innerType:e})}const Re=k({session_id:O(),transcript_path:O(),cwd:O(),permission_mode:O(),hook_event_name:O(),tool_name:O(),tool_input:A(O(),Me())});async function ze(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t;try{t=JSON.parse(e.join(``))}catch{return{ok:!1,raw:e.join(``).slice(0,200),error:`invalid JSON`}}let n=Re.safeParse(t);return n.success?{ok:!0,data:n.data}:{ok:!1,raw:t,error:n.error.message}}const Be=k({command:O(),description:j(O())}),Ve=k({file_path:O()}),He=k({pattern:O(),path:j(O())});function Ue(e){let t=Be.safeParse(e);return t.success?t.data:null}function We(e){let t=Ve.safeParse(e);return t.success?t.data:null}function Ge(e){let t=He.safeParse(e);return t.success?t.data:null}function M(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function Ke(){let e=a.dirname(l(import.meta.url));return a.join(e,`bst-hook.audit.local.jsonl`)}function qe(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let Je=!1;function N(t){try{let n=Ke();Je||=(qe(n),!0);let r={timestamp:new Date().toISOString(),...t};e.appendFileSync(n,JSON.stringify(r)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function P(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function Ye(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){F(t,n,`&&`),n=``,r+=2;continue}if(o===`&`&&!(r+1<e.length&&e[r+1]===`>`)&&!(r>0&&(e[r-1]===`>`||e[r-1]===`<`))){F(t,n,`&`),n=``,r++;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){F(t,n,`||`),n=``,r+=2;continue}if(o===`|`){F(t,n,`|`),n=``,r++;continue}if(o===`;`){F(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function F(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const I=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Xe(e,t,n=!1){if(/[\\n\\r]/.test(t))return{safe:!1,reason:`unquoted newline in command`};for(let t of e){if(t===\"`\")return{safe:!1,reason:`backtick command substitution`};if(t.includes(`$(`))return{safe:!1,reason:`dollar command substitution`};if(t.includes(\"`\"))return{safe:!1,reason:`backtick command substitution`};if(t.includes(`<(`)||t.includes(`>(`)&&!I.has(t))return{safe:!1,reason:`process substitution`}}if(!n)for(let t=0;t<e.length;t++){let n=e[t];if(!I.has(n)){if(n.includes(`<`)&&!n.includes(`<(`))return{safe:!1,reason:`input redirect`};if(n.includes(`>`)){if(n.endsWith(`>/dev/null`)||n.match(/^(&>>|&>|>>|>|1>|2>)$/)&&e[t+1]===`/dev/null`)continue;return{safe:!1,reason:`output redirect to file`}}}}return{safe:!0}}const Ze=[`.sample`,`.example`,`.template`],Qe=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function $e(e){return Ze.some(t=>e.endsWith(t))}function L(e){return $e(a.basename(e))?!1:Qe.test(e)}function et(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&L(e))}const tt=new Set([`status`,`log`,`diff`,`show`,`blame`,`grep`,`shortlog`,`range-diff`,`describe`,`branch`,`remote`,`fetch`,`ls-files`,`ls-tree`,`ls-remote`,`show-ref`,`for-each-ref`,`rev-parse`,`rev-list`,`cat-file`,`diff-tree`,`name-rev`,`merge-base`,`check-ignore`]),nt=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),rt=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),it=new Set([`-C`,`-c`,`--git-dir`,`--work-tree`]);function R(e){let t=e.slice(1);for(let e=0;e<t.length;e++){let n=t[e];if(it.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function at(e){for(let t of e.slice(1))if(rt.has(t)||t===`-o`||t.startsWith(`--output`)||t.startsWith(`--exec-path=`)||t.startsWith(`--pager=`)||t.startsWith(`--upload-pack=`)||t.startsWith(`--receive-pack=`)||t.startsWith(`-o=`)||t.startsWith(`--output=`))return!1;let t=R(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===`list`||t===void 0}return tt.has(t)}function ot(e){let t=R(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];if(t===`list`||t===void 0)return!1}return nt.has(t)}const st=new Set([`push`,`pop`,`apply`,`show`]);function z(e){let t=R(e);if(!t)return!1;if(t===`add`)return!0;if(t===`checkout`)return e.slice(e.indexOf(`checkout`)+1).some(e=>e===`-b`||e===`-B`);if(t===`switch`)return!0;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===void 0?!0:st.has(t)}return!1}const B=new Set(`install.i.add.remove.rm.uninstall.un.update.up.upgrade.list.ls.ll.la.why.view.info.show.outdated.audit.licenses.init.create.link.unlink.pack.publish.config.set.get.store.cache.rebuild.rb.prune.dedupe.dlx.exec.run.patch.patch-commit.recursive.-r`.split(`.`)),ct=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function lt(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!B.has(e))){n=e;break}return n?ct.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const ut=new Set([`view`,`info`,`show`]);function dt(e){let t=e[1];return t!==void 0&&ut.has(t)}function ft(e){let t=e[1],n=`This project uses pnpm.`;return t?t===`install`||t===`i`||t===`ci`?e.slice(2).some(e=>!e.startsWith(`-`))?`${n} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`:`${n} Use \\`pnpm install\\` instead of npm.`:t===`add`?`${n} Use \\`pnpm add <pkg>\\` instead of npm.`:t===`uninstall`||t===`remove`||t===`rm`||t===`un`||t===`r`?`${n} Use \\`pnpm remove <pkg>\\` instead of npm.`:t===`run`||t===`run-script`?`${n} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`:t===`test`||t===`t`||t===`tst`?`${n} Use \\`pnpm test\\` instead of npm.`:t===`start`?`${n} Use \\`pnpm start\\` instead of npm.`:t===`stop`?`${n} Use \\`pnpm stop\\` instead of npm.`:t===`restart`?`${n} Use \\`pnpm restart\\` instead of npm.`:t===`exec`?`${n} Use \\`pnpm exec <cmd>\\` instead of npm.`:t===`list`||t===`ls`||t===`ll`||t===`la`?`${n} Use \\`pnpm list\\` instead of npm.`:t===`why`?`${n} Use \\`pnpm why\\` instead of npm.`:t===`outdated`?`${n} Use \\`pnpm outdated\\` instead of npm.`:t===`audit`?`${n} Use \\`pnpm audit\\` instead of npm.`:t===`update`||t===`up`||t===`upgrade`?`${n} Use \\`pnpm update\\` instead of npm.`:t===`init`?`${n} Use \\`pnpm init\\` instead of npm.`:t===`publish`?`${n} Use \\`pnpm publish\\` instead of npm.`:t===`link`?`${n} Use \\`pnpm link\\` instead of npm.`:t===`unlink`?`${n} Use \\`pnpm unlink\\` instead of npm.`:t===`pack`?`${n} Use \\`pnpm pack\\` instead of npm.`:t===`cache`?`${n} Use \\`pnpm store\\` instead of \\`npm cache\\`.`:t===`config`||t===`set`||t===`get`?`${n} Use \\`pnpm config\\` instead of npm.`:`${n} Use the equivalent pnpm command instead of npm.`:`${n} Use \\`pnpm\\` instead of npm.`}function V(e,t){for(let n of e.slice(1)){if(n.startsWith(`-`)){if(n===`-t`||n.startsWith(`--target-directory`))return!1;continue}if(a.basename(n).startsWith(`.`)||n.startsWith(`~/`)||n===`~`)return!1;let e=a.resolve(t.cwd,n);if(e!==t.cwd&&!e.startsWith(t.cwd+a.sep))return!1}return!0}const pt=new Set([`-exec`,`-execdir`,`-ok`,`-okdir`,`-delete`,`-fprint`,`-fprint0`,`-fprintf`,`-fls`]);function mt(e){return!e.some(e=>pt.has(e))}function ht(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function gt(e){let t=e.findIndex(e=>a.basename(e)===`rm`);if(t===-1)return!1;let n=e.slice(t+1),r=n.filter(e=>e.startsWith(`-`)&&!e.startsWith(`--`)),i=n.filter(e=>e.startsWith(`--`)),o=r.some(e=>/[rR]/.test(e)),s=i.includes(`--recursive`);return o||s}function _t(e){return e.some(e=>e===`777`||e===`0777`)}function vt(e){let t=e.indexOf(`push`);if(t===-1)return!1;let n=e.slice(t+1);return n.some(e=>e===`--force`||e===`-f`&&!n.includes(`--force-with-lease`))}function yt(e){if(!(e.includes(`reset`)&&e.includes(`--hard`)))return!1;if(e.some(e=>e===`main`||e===`master`))return!0;let t=e.filter(e=>e!==`git`&&e!==`reset`&&!e.startsWith(`-`));return t.length===0||t.includes(`HEAD`)}const bt=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>gt(e),reason:\"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"},{name:`sudo`,match:/^sudo$/,action:`deny`,reason:`Don't use sudo. If you need elevated permissions, ask me to do it manually.`},{name:`git-force-push`,match:/^git$/,action:`deny`,when:e=>vt(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>yt(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>ot(e)&&!z(e),reason:`Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.`},{name:`chmod-777`,match:/^chmod$/,action:`deny`,when:e=>_t(e),reason:`Don't use 777 permissions. Use more restrictive permissions.`},{name:`env-file-read`,match:/^(cat|head|tail|less|more|grep)$/,action:`deny`,when:e=>et(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`npx-redirect`,match:/^npx$/,action:`deny`,reason:\"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"}],H=[{name:`vitest`,match:/^vitest$/,action:`allow`},{name:`jest`,match:/^jest$/,action:`allow`},{name:`tsc`,match:/^tsc$/,action:`allow`},{name:`tsdown`,match:/^tsdown$/,action:`allow`},{name:`oxlint`,match:/^oxlint$/,action:`allow`},{name:`oxfmt`,match:/^oxfmt$/,action:`allow`},{name:`eslint`,match:/^eslint$/,action:`allow`},{name:`prettier`,match:/^prettier$/,action:`allow`},{name:`biome`,match:/^biome$/,action:`allow`},{name:`knip`,match:/^knip$/,action:`allow`},{name:`cat`,match:/^cat$/,action:`allow`},{name:`head`,match:/^head$/,action:`allow`},{name:`tail`,match:/^tail$/,action:`allow`},{name:`grep`,match:/^grep$/,action:`allow`},{name:`ls`,match:/^ls$/,action:`allow`},{name:`wc`,match:/^wc$/,action:`allow`},{name:`uniq`,match:/^uniq$/,action:`allow`},{name:`diff`,match:/^diff$/,action:`allow`},{name:`echo`,match:/^echo$/,action:`allow`},{name:`pwd`,match:/^pwd$/,action:`allow`},{name:`which`,match:/^which$/,action:`allow`},{name:`dirname`,match:/^dirname$/,action:`allow`},{name:`basename`,match:/^basename$/,action:`allow`},{name:`realpath`,match:/^realpath$/,action:`allow`},{name:`npm`,match:/^npm$/,action:`allow`,when:e=>dt(e)},{name:`test`,match:/^test$/,action:`allow`},{name:`bracket-test`,match:/^\\[$/,action:`allow`},{name:`cd`,match:/^cd$/,action:`allow`},{name:`true`,match:/^true$/,action:`allow`},{name:`colon`,match:/^:$/,action:`allow`},{name:`find`,match:/^find$/,action:`allow`,when:e=>mt(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>ht(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>at(e)},{name:`git-safe-write`,match:/^git$/,action:`allow`,when:e=>z(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>lt(e)},{name:`cp`,match:/^cp$/,action:`allow`,when:(e,t)=>V(e,t)},{name:`mkdir`,match:/^mkdir$/,action:`allow`,when:(e,t)=>V(e,t)},{name:`touch`,match:/^touch$/,action:`allow`,when:(e,t)=>V(e,t)}],xt=`.claude/hooks/bst-hook.settings.json`,St=k({allow:Pe(O())});function Ct(e,t){try{return u(`git`,[`ls-files`,`--error-unmatch`,t],{cwd:e,stdio:`ignore`}),u(`git`,[`diff`,`--quiet`,`HEAD`,`--`,t],{cwd:e,stdio:`ignore`}),!0}catch{return!1}}function wt(e){let r=W(e)??e,i=s(r,xt);if(!t(i)||!Ct(r,xt))return[];try{let e=n(i,`utf-8`),t=JSON.parse(e),r=St.safeParse(t);return r.success?r.data.allow.filter(e=>typeof e==`string`&&e.length>0):[]}catch{return[]}}function Tt(e,t,n){let r=n.split(/\\s+/).filter(Boolean);if(r.length===0||r[0]!==e)return!1;let i=r.slice(1),a=t.slice(1);for(let e=0;e<i.length;e++)if(e>=a.length||a[e]!==i[e])return!1;return!0}const Et=k({scripts:A(O(),Me())});function U(e,t){let n=Pt(t);if(!n)return null;let r=n[e];if(typeof r!=`string`)return null;let i=n[`pre${e}`],a=n[`post${e}`];return{pre:typeof i==`string`?i:null,main:r,post:typeof a==`string`?a:null}}function Dt(e,t,n){let r=At(t,n);if(!r)return null;let i=U(e,r);return i?{...i,pkgDir:r}:null}function Ot(e,t){let n=kt(t);if(!n)return null;let r=[];for(let t of n){let n=U(e,t);n&&r.push({...n,pkgDir:t})}return r}function W(e){let n=c(e);for(;;){if(t(s(n,`pnpm-workspace.yaml`)))return n;let e=o(n);if(e===n)return null;n=e}}function kt(e){let t=jt(e);if(!t)return null;let n=[];for(let a of t){if(!a.endsWith(`/*`))continue;let t=c(e,a.slice(0,-2)),o;try{o=r(t)}catch{continue}for(let e of o){let r=s(t,e);try{if(!i(r).isDirectory())continue}catch{continue}n.push(r)}}return n.length>0?n:null}function At(e,t){let n=kt(t);if(!n)return null;for(let t of n){let n=Nt(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function jt(e){try{let t=n(s(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),r=t.findIndex(e=>/^packages\\s*:/.test(e));if(r===-1)return null;let i=[];for(let e=r+1;e<t.length;e++){let n=t[e],r=/^\\s+-\\s+(.+)$/.exec(n);if(!r)break;i.push(r[1].trim().replace(/['\"`]/g,``))}return i.length>0?i:null}catch{return null}}const Mt=k({name:O()});function Nt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=Mt.safeParse(JSON.parse(t));return r.success?r.data.name:null}catch{return null}}function Pt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=Et.safeParse(JSON.parse(t));return r.success?r.data.scripts:null}catch{return null}}function Ft(e){return Ct(e,`package.json`)}const It=new Set([`LD_PRELOAD`,`LD_LIBRARY_PATH`,`DYLD_INSERT_LIBRARIES`,`NODE_OPTIONS`,`PYTHONPATH`,`PYTHONSTARTUP`,`PERL5OPT`,`PERL5LIB`,`RUBYOPT`,`RUBYLIB`,`BASH_ENV`,`ENV`,`npm_config_prefix`,`npm_config_registry`,`EDITOR`,`VISUAL`]),Lt=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function Rt(e){return It.has(e)?!0:Lt.some(t=>e.startsWith(t))}function zt(e,t){let n=P(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let i=r[1];return a.isAbsolute(i)?i:a.resolve(t.cwd,i)}function G(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=Ye(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=zt(e.command,o);t&&(o={...o,cwd:t})}let t=Bt(e.command,o);if(t.action===`deny`)return t;t.action===`passthrough`&&(i||=t),t.action===`allow`&&(a=t)}return i||(r.length===1&&a?a:{action:`allow`,rule:`all-segments`,reason:null})}function K(e,t){let n=e.pkgDir??t.cwd,r=t.insideResolvedScript??Ft(n)?{...t,insideResolvedScript:!0}:t;if(e.pre){let t=G(e.pre,r);if(t.action!==`allow`)return t}let i=G(e.main,r);if(i.action!==`allow`)return i;if(e.post){let t=G(e.post,r);if(t.action!==`allow`)return t}return`allow`}function Bt(e,t){let n=e.trim();if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=P(e);if(!r.ok)return{action:`passthrough`,rule:null,reason:r.error};let i=r.tokens;if(i.length===0)return{action:`passthrough`,rule:null,reason:null};let o=0;for(;o<i.length&&/^\\w+=/.test(i[o]);){let e=i[o].split(`=`)[0];if(Rt(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};o++}if(o>=i.length)return{action:`allow`,rule:`env-only`,reason:null};i=i.slice(o);let s=Xe(i,e,t.insideResolvedScript);if(!s.safe)return{action:`passthrough`,rule:null,reason:s.reason};if(!t.insideResolvedScript)for(let e=0;e<i.length;e++){let t=i[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(i[e+1]===`/dev/null`)continue;return{action:`passthrough`,rule:null,reason:`output redirect to file`}}if(t===`<`)return{action:`passthrough`,rule:null,reason:`input redirect`}}}let c=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);i=i.filter((e,t)=>!(c.has(e)||(e===`>`||e===`>>`)&&i[t+1]===`/dev/null`||e===`/dev/null`&&t>0&&(i[t-1]===`>`||i[t-1]===`>>`)));let l=a.basename(i[0]);if(l===`command`&&i.length>1)return G(i.slice(1).join(` `),{...t,depth:t.depth+1});if(l===`env`){let e=1;for(;e<i.length&&/^\\w+=/.test(i[e]);){let t=i[e].split(`=`)[0];if(Rt(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=i.length?{action:`allow`,rule:`env-only`,reason:null}:G(i.slice(e).join(` `),{...t,depth:t.depth+1})}if(l===`pnpm`)return Vt(i,t);if(l===`npm`)return Ut(i);if(l===`bash`||l===`sh`||l===`zsh`)return Kt(i,t);if(l===`xargs`)return Jt(i,t);if(l===`node`&&(i.includes(`-e`)||i.includes(`-p`)||i.includes(`--eval`)||i.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(l===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let e of bt)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`deny`,rule:e.name,reason:e.reason??null};for(let e of H)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`allow`,rule:e.name,reason:null};if(t.repoAllowPatterns){for(let e of t.repoAllowPatterns)if(Tt(l,i,e))return{action:`allow`,rule:`repo:${l}`,reason:null}}return t.insideResolvedScript?{action:`allow`,rule:`resolved-script`,reason:null}:{action:`passthrough`,rule:null,reason:`no rule matched: ${l}`}}function Vt(e,t){let n=e.slice(1),r,i=-1,o=!1,s=!1,c;for(let a=0;a<n.length;a++){let l=n[a];if(l===`--filter`||l.startsWith(`--filter=`))return Wt(e,n,a,t);if(l===`-r`||l===`--recursive`){o=!0;continue}if(l===`-w`||l===`--workspace-root`){s=!0;continue}if(l===`-C`||l===`--dir`){c=n[++a];continue}if(l.startsWith(`-C=`)||l.startsWith(`--dir=`)){c=l.split(`=`,2)[1];continue}if(!(l.startsWith(`-`)&&!B.has(l))){r=l,i=a;break}}if(!r)return{action:`passthrough`,rule:null,reason:null};if(r===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(r===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(r===`exec`){let e=n.slice(i+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:G(e.join(` `),{...t,depth:t.depth+1})}let l;if(r===`run`){let e=n[i+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};l=e}else if(!B.has(r))l=r;else{for(let n of H)if(n.match.test(`pnpm`)&&(!n.when||n.when(e,t)))return{action:`allow`,rule:n.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}if(o)return Ht(l,t);if(s){let e=W(t.cwd);return e?q(l,{...t,cwd:e}):{action:`passthrough`,rule:null,reason:`workspace root not found`}}if(c){let e=a.resolve(t.cwd,c);return q(l,{...t,cwd:e})}return q(l,t)}function q(e,t){let n=U(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r=K(n,{...t,depth:t.depth+1});return r===`allow`?{action:`allow`,rule:`pnpm-script:${e}`,reason:null}:r}function Ht(e,t){let n=Ot(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n=K(e,{...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd});if(n!==`allow`)return n}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function Ut(e){for(let t of H)if(t.match.test(`npm`)&&(!t.when||t.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:t.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:ft(e)}}function Wt(e,t,n,r){let i=t[n],a,o;if(i.startsWith(`--filter=`))a=i.slice(9),o=n+1;else{if(a=t[n+1],!a||a.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};o=n+2}if(!a)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let s;for(let e=o;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!B.has(n))){s=n;break}}if(!s)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(s===`install`||s===`i`)return t.slice(t.indexOf(s)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(s===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(s===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(s))return{action:`allow`,rule:`pnpm`,reason:null};if(s===`exec`){let e=t.indexOf(`exec`,o),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:G(n.join(` `),{...r,depth:r.depth+1})}let c=s===`run`?t[t.indexOf(`run`,o)+1]:s;if(!c||c.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(B.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=Dt(c,a,r.cwd);if(!l)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let u=K(l,{...r,depth:r.depth+1,cwd:l.pkgDir??r.cwd});return u===`allow`?{action:`allow`,rule:`pnpm-filter:${a}:${c}`,reason:null}:u}const Gt=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function Kt(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||Gt.has(r))return{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}}let r=e[n+1];return r?e.length>n+2?{action:`passthrough`,rule:null,reason:`shell -c with positional args`}:n===1?G(r,{...t,depth:t.depth+1}):{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}:{action:`passthrough`,rule:null,reason:`shell -c without command`}}const J=new Set([`-I`,`-d`,`-n`,`-P`,`-s`,`-L`,`-E`,`-a`,`--max-args`,`--max-procs`,`--delimiter`,`--replace`,`--max-lines`,`--max-chars`,`--arg-file`,`--process-slot-var`]),qt=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function Jt(e,t){let n=1;for(;n<e.length;){let t=e[n];if(!t.startsWith(`-`))break;if(J.has(t)){n+=2;continue}let r=t.indexOf(`=`);if(r!==-1){let e=t.slice(0,r);if(J.has(e)){n++;continue}}if(t.length>2&&J.has(t.slice(0,2))){n++;continue}if(qt.has(t)){n++;continue}return{action:`passthrough`,rule:null,reason:`xargs with unrecognised flags`}}return n>=e.length?{action:`passthrough`,rule:null,reason:`xargs without command`}:G(e.slice(n).join(` `),{...t,depth:t.depth+1})}const Yt=new Set([`Read`,`Glob`,`Grep`]),Y=new Set([`Write`,`Edit`]),Xt=[{name:`env-file-read`,tools:Yt,action:`deny`,when:e=>L(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-write`,tools:Y,action:`deny`,when:e=>L(e),reason:`Don't write to .env files. These contain secrets.`},{name:`hooks-folder-write`,tools:Y,action:`deny`,when:(e,t)=>{let n=a.resolve(t.cwd,e),r=a.join(t.cwd,`.claude`,`hooks`);return n.startsWith(r+a.sep)||n===r},reason:`Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.`},{name:`claude-settings-write`,tools:Y,action:`deny`,when:(e,t)=>a.resolve(t.cwd,e)===a.join(t.cwd,`.claude`,`settings.json`),reason:`Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.`},{name:`outside-cwd-write`,tools:Y,action:`deny`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n!==t.cwd&&!n.startsWith(t.cwd+a.sep)},reason:`Don't write files outside the project directory.`}],Zt=[{name:`cwd-read`,tools:Yt,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}},{name:`cwd-write`,tools:Y,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}}],Qt=new Set([`Read`,`Write`,`Edit`]),$t=new Set([`Glob`,`Grep`]);function en(e,t,n){return Qt.has(e)?We(t)?.file_path??null:$t.has(e)?Ge(t)?.path??n:null}function tn(e,t,n){let r=en(e,t,n.cwd);if(r===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of Xt)if(t.tools.has(e)&&t.when(r,n))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of Zt)if(t.tools.has(e)&&t.when(r,n))return{action:`allow`,rule:t.name,reason:null};return{action:`passthrough`,rule:null,reason:`no file rule matched`}}const X=await ze();X.ok||(process.stderr.write(`[bst-hook] invalid hook input: ${X.error}\\n`),N({sessionId:`unknown`,cwd:`unknown`,command:`<invalid input: ${X.error}>`,decision:`passthrough`,rule:null,reason:`validation failed: ${X.error}`}),process.exit(0));const Z=X.data;Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);const nn=wt(Z.cwd);let Q,$;if(Z.tool_name===`Bash`){let e=Ue(Z.tool_input);e||(N({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:`<invalid Bash input: ${JSON.stringify(Z.tool_input).slice(0,200)}>`,decision:`passthrough`,rule:null,reason:`Bash tool_input missing or invalid 'command' field`}),process.exit(0)),Q=G(e.command,{cwd:Z.cwd,depth:0,repoAllowPatterns:nn}),$=e.command}else{let e=W(Z.cwd)??process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=tn(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}N({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?M(`allow`):Q.action===`deny`&&M(`deny`,Q.reason);export{};", "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\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-write**: Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\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- **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- **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 safe writes**: git-safe-write (add, switch -c, checkout -b, stash push/pop/apply/show)\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)\n\n### File tools\n\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` (file moves)\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")];
2727
2729
  }
2728
2730
  //#endregion
2729
2731
  //#region src/generators/claude-settings.ts
@@ -2766,9 +2768,9 @@ function buildSettings(ctx) {
2766
2768
  const allow = [];
2767
2769
  const enabledPlugins = { "code-simplifier@claude-plugins-official": true };
2768
2770
  const extraKnownMarketplaces = {};
2769
- if (ctx.config.structure !== "monorepo") {
2770
- if (ctx.packageJson ? packageHasWebUIDeps(ctx.packageJson) : false) enabledPlugins["frontend-design@claude-plugins-official"] = true;
2771
- }
2771
+ if (ctx.config.structure === "monorepo") {
2772
+ if (getMonorepoPackages(ctx.targetDir).some((pkg) => hasWebUIDeps(pkg.dir))) enabledPlugins["frontend-design@claude-plugins-official"] = true;
2773
+ } else if (ctx.packageJson ? packageHasWebUIDeps(ctx.packageJson) : false) enabledPlugins["frontend-design@claude-plugins-official"] = true;
2772
2774
  return {
2773
2775
  permissions: {
2774
2776
  allow,
@@ -2778,7 +2780,7 @@ function buildSettings(ctx) {
2778
2780
  matcher: "Bash|Read|Write|Edit|Glob|Grep",
2779
2781
  hooks: [{
2780
2782
  type: "command",
2781
- command: `node "$CLAUDE_PROJECT_DIR/.claude/hooks/bst-hook.mjs"`
2783
+ command: `node "$(git rev-parse --show-toplevel)/.claude/hooks/bst-hook.mjs"`
2782
2784
  }]
2783
2785
  }] },
2784
2786
  instructions: [
@@ -2837,7 +2839,23 @@ function writeOrMergeSettings(ctx, filePath, generated) {
2837
2839
  for (const [event, generatedMatchers] of Object.entries(generated.hooks)) {
2838
2840
  const existingMatchers = mergedHooks[event] ?? [];
2839
2841
  for (const gm of generatedMatchers) {
2840
- if (existingMatchers.some((m) => m.matcher === gm.matcher && m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER)))) continue;
2842
+ const existingIdx = existingMatchers.findIndex((m) => m.matcher === gm.matcher && m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER)));
2843
+ if (existingIdx !== -1) {
2844
+ const existingEntry = existingMatchers[existingIdx];
2845
+ const genCommand = gm.hooks.find((h) => h.command.includes(BST_HOOK_MARKER))?.command;
2846
+ const curCommand = existingEntry.hooks.find((h) => h.command.includes(BST_HOOK_MARKER))?.command;
2847
+ if (genCommand && curCommand && genCommand !== curCommand) {
2848
+ mergedHooks[event] = (mergedHooks[event] ?? []).map((m, i) => i === existingIdx ? {
2849
+ ...m,
2850
+ hooks: m.hooks.map((h) => h.command.includes(BST_HOOK_MARKER) ? {
2851
+ ...h,
2852
+ command: genCommand
2853
+ } : h)
2854
+ } : m);
2855
+ hookChanges++;
2856
+ }
2857
+ continue;
2858
+ }
2841
2859
  if (gm.matcher.includes("|")) {
2842
2860
  const before = (mergedHooks[event] ?? []).length;
2843
2861
  mergedHooks[event] = removeObsoleteBstHookEntries(mergedHooks[event] ?? [], gm.matcher);
@@ -2888,19 +2906,16 @@ async function generateClaudeSettings(ctx) {
2888
2906
  const results = [];
2889
2907
  results.push(writeOrMergeSettings(ctx, ".claude/settings.json", buildSettings(ctx)));
2890
2908
  if (ctx.config.structure === "monorepo") for (const pkg of getMonorepoPackages(ctx.targetDir)) {
2891
- if (!hasWebUIDeps(pkg.dir)) continue;
2892
2909
  const pkgRelDir = path.relative(ctx.targetDir, pkg.dir);
2893
- const pkgSettingsPath = path.join(pkgRelDir, ".claude/settings.json");
2894
- results.push(writeOrMergeSettings(ctx, pkgSettingsPath, {
2895
- permissions: {
2896
- allow: [],
2897
- deny: []
2898
- },
2899
- hooks: {},
2900
- instructions: [],
2901
- enabledPlugins: { "frontend-design@claude-plugins-official": true },
2902
- extraKnownMarketplaces: {}
2903
- }));
2910
+ const claudeDir = path.join(pkgRelDir, ".claude");
2911
+ if (ctx.exists(claudeDir)) {
2912
+ ctx.remove(claudeDir);
2913
+ results.push({
2914
+ filePath: claudeDir,
2915
+ action: "removed",
2916
+ description: `Removed subpackage ${claudeDir} (plugins consolidated at root)`
2917
+ });
2918
+ }
2904
2919
  }
2905
2920
  return results;
2906
2921
  }
@@ -3349,7 +3364,7 @@ async function runGenerators(ctx) {
3349
3364
  function generateMigratePrompt(results, config, detected) {
3350
3365
  const sections = [];
3351
3366
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3352
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.46.0 repo:sync\` on ${timestamp}_\n`);
3367
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.48.0 repo:sync\` on ${timestamp}_\n`);
3353
3368
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
3354
3369
  if (changed.length > 0) {
3355
3370
  const items = changed.map((r) => {
@@ -3516,10 +3531,13 @@ async function runInit(config, options = {}) {
3516
3531
  });
3517
3532
  const created = results.filter((r) => r.action === "created");
3518
3533
  const updated = results.filter((r) => r.action === "updated");
3519
- const hasChanges = created.length > 0 || updated.length > 0 || archivedFiles.length > 0;
3520
- const prompt = generateMigratePrompt(results, config, detected);
3534
+ const removed = results.filter((r) => r.action === "removed");
3535
+ const hasChanges = created.length > 0 || updated.length > 0 || removed.length > 0 || archivedFiles.length > 0;
3521
3536
  const promptPath = ".tooling-migrate.md";
3522
- ctx.write(promptPath, prompt);
3537
+ if (!options.noPrompt) {
3538
+ const prompt = generateMigratePrompt(results, config, detected);
3539
+ ctx.write(promptPath, prompt);
3540
+ }
3523
3541
  if (!hasChanges && options.noPrompt) {
3524
3542
  log.success("Repository is up to date.");
3525
3543
  return results;
@@ -3534,6 +3552,7 @@ async function runInit(config, options = {}) {
3534
3552
  const summaryLines = [];
3535
3553
  if (created.length > 0) summaryLines.push(`Created: ${created.map((r) => r.filePath).join(", ")}`);
3536
3554
  if (updated.length > 0) summaryLines.push(`Updated: ${updated.map((r) => r.filePath).join(", ")}`);
3555
+ if (removed.length > 0) summaryLines.push(`Removed: ${removed.map((r) => r.filePath).join(", ")}`);
3537
3556
  note(summaryLines.join("\n"), "Summary");
3538
3557
  if (!options.noPrompt) log.info(`Migration prompt written to ${promptPath}`);
3539
3558
  const packageJsonChanged = results.some((r) => r.filePath === "package.json" && (r.action === "created" || r.action === "updated"));
@@ -3611,8 +3630,8 @@ const syncCommand = defineCommand({
3611
3630
  let config;
3612
3631
  if (args.yes || !isFirstRun) {
3613
3632
  const detected = buildDefaultConfig(targetDir, {
3614
- eslintPlugin: args["eslint-plugin"] === true ? true : void 0,
3615
- noCi: args["no-ci"] === true ? true : void 0
3633
+ eslintPlugin: typeof args["eslint-plugin"] === "boolean" ? args["eslint-plugin"] : void 0,
3634
+ noCi: typeof args["no-ci"] === "boolean" ? args["no-ci"] : void 0
3616
3635
  });
3617
3636
  config = saved ? mergeWithSavedConfig(detected, saved) : detected;
3618
3637
  } else config = await runInitPrompts(targetDir, saved);
@@ -3629,9 +3648,10 @@ async function runCheck(targetDir) {
3629
3648
  const { ctx, pendingWrites } = createDryRunContext(saved ? mergeWithSavedConfig(detected, saved) : detected);
3630
3649
  logDetectionSummary(ctx);
3631
3650
  const actionable = (await runGenerators(ctx)).filter((r) => {
3651
+ if (r.action === "removed") return true;
3632
3652
  if (r.action !== "created" && r.action !== "updated") return false;
3633
3653
  const newContent = pendingWrites.get(r.filePath);
3634
- if (newContent && r.action === "updated") {
3654
+ if (newContent !== void 0 && r.action === "updated") {
3635
3655
  const existingPath = path.join(targetDir, r.filePath);
3636
3656
  const existing = existsSync(existingPath) ? readFileSync(existingPath, "utf-8") : void 0;
3637
3657
  if (existing && contentEqual(r.filePath, existing, newContent)) return false;
@@ -3646,7 +3666,7 @@ async function runCheck(targetDir) {
3646
3666
  for (const r of actionable) {
3647
3667
  log.info(` ${r.action}: ${r.filePath} — ${r.description}`);
3648
3668
  const newContent = pendingWrites.get(r.filePath);
3649
- if (!newContent) continue;
3669
+ if (newContent === void 0) continue;
3650
3670
  const existingPath = path.join(targetDir, r.filePath);
3651
3671
  const existing = existsSync(existingPath) ? readFileSync(existingPath, "utf-8") : void 0;
3652
3672
  if (isBundledFile(r.filePath)) log.info(` bundled file changed (${formatBytes(existing?.length ?? 0)} → ${formatBytes(newContent.length)})`);
@@ -4692,6 +4712,32 @@ function mergeGitHub(dryRun) {
4692
4712
  }
4693
4713
  //#endregion
4694
4714
  //#region src/release/simple.ts
4715
+ /**
4716
+ * Resolve the bump/first-release/release-as values from CLI flags and the
4717
+ * RELEASE_BUMP env var. CLI flags take precedence over the env var.
4718
+ *
4719
+ * RELEASE_BUMP values: "auto" (or empty) → no flags,
4720
+ * "major"/"minor"/"patch" → releaseAs, "first-release" → firstRelease.
4721
+ */
4722
+ function resolveReleaseBump(flagFirstRelease, flagReleaseAs, envBump) {
4723
+ if (flagFirstRelease || flagReleaseAs) return {
4724
+ firstRelease: flagFirstRelease,
4725
+ releaseAs: flagReleaseAs
4726
+ };
4727
+ const bump = envBump?.trim();
4728
+ if (!bump || bump === "auto") return {
4729
+ firstRelease: false,
4730
+ releaseAs: void 0
4731
+ };
4732
+ if (bump === "first-release") return {
4733
+ firstRelease: true,
4734
+ releaseAs: void 0
4735
+ };
4736
+ return {
4737
+ firstRelease: false,
4738
+ releaseAs: bump
4739
+ };
4740
+ }
4695
4741
  /** Build the commit-and-tag-version args. */
4696
4742
  function buildArgs(config) {
4697
4743
  const args = ["exec", "commit-and-tag-version"];
@@ -4877,6 +4923,7 @@ const releaseSimpleCommand = defineCommand({
4877
4923
  };
4878
4924
  else platform = { type: "github" };
4879
4925
  }
4926
+ const bump = resolveReleaseBump(args["first-release"] === true, args["release-as"], process.env["RELEASE_BUMP"]);
4880
4927
  const config = {
4881
4928
  cwd,
4882
4929
  dryRun: args["dry-run"] === true,
@@ -4884,9 +4931,9 @@ const releaseSimpleCommand = defineCommand({
4884
4931
  noPush: args["no-push"] === true,
4885
4932
  noSlidingTags: args["no-sliding-tags"] === true,
4886
4933
  noRelease,
4887
- firstRelease: args["first-release"] === true,
4888
- releaseAs: args["release-as"],
4889
- prerelease: args.prerelease,
4934
+ firstRelease: bump.firstRelease,
4935
+ releaseAs: bump.releaseAs,
4936
+ prerelease: args.prerelease ?? process.env["RELEASE_PRERELEASE"] ?? void 0,
4890
4937
  platform
4891
4938
  };
4892
4939
  if ((await runSimpleRelease(createRealExecutor(), config)).pushed) {
@@ -6035,8 +6082,7 @@ const webhookSendCommand = defineCommand({
6035
6082
  args: {
6036
6083
  url: {
6037
6084
  type: "positional",
6038
- description: "Webhook URL to POST to",
6039
- required: true
6085
+ description: "Webhook URL to POST to. If empty or missing, the command exits successfully (no-op)."
6040
6086
  },
6041
6087
  tag: {
6042
6088
  type: "string",
@@ -6060,6 +6106,10 @@ const webhookSendCommand = defineCommand({
6060
6106
  }
6061
6107
  },
6062
6108
  async run({ args }) {
6109
+ if (!args.url) {
6110
+ log.info("No webhook URL configured — skipping post-release hook");
6111
+ return;
6112
+ }
6063
6113
  const secret = process.env["WEBHOOK_SECRET"];
6064
6114
  const payload = args.tag || args.repository ? buildSimplePayload({
6065
6115
  tag: args.tag ?? "",
@@ -6076,7 +6126,7 @@ const webhookSendCommand = defineCommand({
6076
6126
  const main = defineCommand({
6077
6127
  meta: {
6078
6128
  name: "bst",
6079
- version: "0.46.0",
6129
+ version: "0.48.0",
6080
6130
  description: "Bootstrap and maintain standardized TypeScript project tooling"
6081
6131
  },
6082
6132
  subCommands: {
@@ -6096,7 +6146,7 @@ const main = defineCommand({
6096
6146
  "webhook:send": webhookSendCommand
6097
6147
  }
6098
6148
  });
6099
- console.log(`@bensandee/tooling v0.46.0`);
6149
+ console.log(`@bensandee/tooling v0.48.0`);
6100
6150
  async function run() {
6101
6151
  await runMain(main);
6102
6152
  process.exit(process.exitCode ?? 0);
package/dist/index.d.mts CHANGED
@@ -57,7 +57,7 @@ interface ProjectConfig {
57
57
  /** Result from a single generator: what file was written and how. */
58
58
  interface GeneratorResult {
59
59
  filePath: string;
60
- action: "created" | "updated" | "skipped" | "archived";
60
+ action: "created" | "updated" | "skipped" | "archived" | "removed";
61
61
  /** Human-readable description of what changed */
62
62
  description: string;
63
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,8 +54,8 @@
54
54
  "prettier": "3.8.1",
55
55
  "release-it": "19.2.4",
56
56
  "tsdown": "0.21.6",
57
- "@bensandee/claude-hooks": "0.6.3",
58
- "@bensandee/config": "0.10.1"
57
+ "@bensandee/config": "0.10.1",
58
+ "@bensandee/claude-hooks": "0.6.5"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "tsdown",