@bensandee/tooling 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -8
  2. package/dist/bin.mjs +243 -127
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -89,14 +89,14 @@ To skip a workflow file entirely, add `# @bensandee/tooling:ignore` as the first
89
89
 
90
90
  ### Release management
91
91
 
92
- | Command | Description |
93
- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
94
- | `tooling release:changesets` | Changesets version/publish for Forgejo CI. Flags: `--dry-run`, `--verbose`. Env: `FORGEJO_SERVER_URL`, `FORGEJO_REPOSITORY`, `RELEASE_TOKEN`. |
95
- | `tooling release:simple` | Streamlined release using commit-and-tag-version. Flags: `--release-as`, `--first-release`, `--prerelease`, `--verbose`. |
96
- | `tooling release:trigger` | Trigger a release workflow. |
97
- | `tooling forgejo:create-release` | Create a Forgejo release from a tag. |
98
- | `tooling changesets:merge` | Merge a changesets version PR. |
99
- | `tooling webhook:send <url>` | POST to a webhook URL with HMAC-SHA256 auth. Secret from `WEBHOOK_SECRET` env var. |
92
+ | Command | Description |
93
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
94
+ | `tooling release:changesets` | Changesets version/publish for Forgejo CI. Flags: `--dry-run`, `--verbose`. Env: `FORGEJO_SERVER_URL`, `FORGEJO_REPOSITORY`, `RELEASE_TOKEN`. |
95
+ | `tooling release:simple` | Streamlined release using commit-and-tag-version. Flags: `--release-as`, `--first-release`, `--prerelease`, `--verbose`. |
96
+ | `tooling release:trigger` | Trigger a release workflow. |
97
+ | `tooling forgejo:create-release` | Create a Forgejo release from a tag. |
98
+ | `tooling changesets:merge` | Merge a changesets version PR. |
99
+ | `tooling webhook:send <url>` | POST a structured JSON release payload with HMAC-SHA256 auth. Flags: `--tag`, `--repository`, `--server-url`, `--actor`, `--prerelease`. Secret from `WEBHOOK_SECRET` env var. |
100
100
 
101
101
  #### `release:simple`
102
102
 
package/dist/bin.mjs CHANGED
@@ -1572,7 +1572,7 @@ async function generatePackageJson(ctx) {
1572
1572
  const devDeps = { ...ROOT_DEV_DEPS };
1573
1573
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1574
1574
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.1";
1575
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.44.0";
1575
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.45.0";
1576
1576
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
1577
1577
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
1578
1578
  "@changesets/cli": "2.30.0",
@@ -2706,7 +2706,7 @@ function writeIfChanged(ctx, filePath, content, label) {
2706
2706
  };
2707
2707
  }
2708
2708
  async function generateClaudeHooks(ctx) {
2709
- return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.1\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\";Object.freeze({status:`aborted`});function u(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 d=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const f={};function p(e){return e&&Object.assign(f,e),f}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 m(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 h=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function g(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ie(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 ae(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 oe(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})},se=u(`$ZodError`,S),C=u(`$ZodError`,S,{Parent:Error}),ce=(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 d;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,p())));throw h(t,i?.callee),t}return o.value})(C),le=(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,p())));throw h(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 d;return a.issues.length?{success:!1,error:new(e??se)(a.issues.map(e=>x(e,i,p())))}:{success:!0,data:a.value}})(C),ue=(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,p())))}:{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},T=u(`$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 d;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 d;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 d;return o.then(e=>t(e,r,a))}return t(o,r,a)}}m(e,`~standard`,()=>({validate:t=>{try{let n=w(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return ue(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),me=u(`$ZodString`,(e,t)=>{T.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=u(`$ZodUnknown`,(e,t)=>{T.init(e,t),e._zod.parse=e=>e});function E(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 ge(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=oe(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function _e(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=>E(e,n,i,t,u))):E(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 ve=u(`$ZodObject`,(e,t)=>{if(T.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(()=>ge(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=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=>E(n,t,e,s,r))):E(i,t,e,s,r)}return i?_e(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),ye=u(`$ZodRecord`,(e,t)=>{T.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!ie(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,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(...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 be(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const xe=u(`$ZodOptional`,(e,t)=>{T.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(`^(${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=>be(t,e.value)):be(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var Se,Ce=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 we(){return new Ce}(Se=globalThis).__zod_globalRegistry??(Se.__zod_globalRegistry=we()),globalThis.__zod_globalRegistry;function Te(e,t){return new e({type:`string`,..._(t)})}function Ee(e){return new e({type:`unknown`})}const D=u(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);T.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>ce(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>w(e,t,n),e.parseAsync=async(t,n)=>le(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>ue(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)=>ae(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),De=u(`ZodMiniString`,(e,t)=>{me.init(e,t),D.init(e,t)});function O(e){return Te(De,e)}const Oe=u(`ZodMiniUnknown`,(e,t)=>{he.init(e,t),D.init(e,t)});function k(){return Ee(Oe)}const ke=u(`ZodMiniObject`,(e,t)=>{ve.init(e,t),D.init(e,t),m(e,`shape`,()=>t.shape)});function A(e,t){return new ke({type:`object`,shape:e??{},..._(t)})}const Ae=u(`ZodMiniRecord`,(e,t)=>{ye.init(e,t),D.init(e,t)});function j(e,t,n){return new Ae({type:`record`,keyType:e,valueType:t,..._(n)})}const je=u(`ZodMiniOptional`,(e,t)=>{xe.init(e,t),D.init(e,t)});function M(e){return new je({type:`optional`,innerType:e})}const Me=A({session_id:O(),transcript_path:O(),cwd:O(),permission_mode:O(),hook_event_name:O(),tool_name:O(),tool_input:j(O(),k())});async function Ne(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Me.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Pe=A({command:O(),description:M(O())}),Fe=A({file_path:O()}),Ie=A({pattern:O(),path:M(O())});function Le(e){let t=Pe.safeParse(e);return t.success?t.data:null}function Re(e){let t=Fe.safeParse(e);return t.success?t.data:null}function ze(e){let t=Ie.safeParse(e);return t.success?t.data:null}function N(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function Be(){let e=a.dirname(l(import.meta.url));return a.join(e,`bst-hook.audit.local.jsonl`)}function Ve(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let He=!1;function Ue(t){try{let n=Be();He||=(Ve(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 We(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 Ge=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Ke(e,t){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(`>(`)&&!Ge.has(t))return{safe:!1,reason:`process substitution`}}for(let t=0;t<e.length;t++){let n=e[t];if(!Ge.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 qe=[`.sample`,`.example`,`.template`],Je=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function Ye(e){return qe.some(t=>e.endsWith(t))}function I(e){return Ye(a.basename(e))?!1:Je.test(e)}function Xe(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&I(e))}const Ze=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`]),Qe=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),$e=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),et=new Set([`-C`,`-c`,`--git-dir`,`--work-tree`]);function L(e){let t=e.slice(1);for(let e=0;e<t.length;e++){let n=t[e];if(et.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function tt(e){for(let t of e.slice(1))if($e.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=L(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===`list`||t===void 0}return Ze.has(t)}function nt(e){let t=L(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];if(t===`list`||t===void 0)return!1}return Qe.has(t)}const rt=new Set([`push`,`pop`,`apply`,`show`]);function R(e){let t=L(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:rt.has(t)}return!1}const z=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(`.`)),it=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function at(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!z.has(e))){n=e;break}return n?it.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const ot=new Set([`view`,`info`,`show`]);function st(e){let t=e[1];return t!==void 0&&ot.has(t)}function ct(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 B(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 lt=new Set([`-exec`,`-execdir`,`-ok`,`-okdir`,`-delete`,`-fprint`,`-fprint0`,`-fprintf`,`-fls`]);function ut(e){return!e.some(e=>lt.has(e))}function dt(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function ft(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 pt(e){return e.some(e=>e===`777`||e===`0777`)}function mt(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 ht(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 gt=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>ft(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=>mt(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>ht(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>nt(e)&&!R(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=>pt(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=>Xe(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.\"}],V=[{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=>st(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=>ut(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>dt(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>tt(e)},{name:`git-safe-write`,match:/^git$/,action:`allow`,when:e=>R(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>at(e)},{name:`cp`,match:/^cp$/,action:`allow`,when:(e,t)=>B(e,t)},{name:`mkdir`,match:/^mkdir$/,action:`allow`,when:(e,t)=>B(e,t)},{name:`touch`,match:/^touch$/,action:`allow`,when:(e,t)=>B(e,t)}],_t=A({scripts:j(O(),k())});function H(e,t){let n=Tt(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 vt(e,t,n){let r=xt(t,n);if(!r)return null;let i=H(e,r);return i?{...i,pkgDir:r}:null}function yt(e,t){let n=U(t);if(!n)return null;let r=[];for(let t of n){let n=H(e,t);n&&r.push({...n,pkgDir:t})}return r}function bt(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 U(e){let t=St(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 xt(e,t){let n=U(t);if(!n)return null;for(let t of n){let n=wt(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function St(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 Ct=A({name:O()});function wt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=Ct.safeParse(JSON.parse(t));return r.success?r.data.name:null}catch{return null}}function Tt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=_t.safeParse(JSON.parse(t));return r.success?r.data.scripts:null}catch{return null}}const Et=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`]),Dt=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function W(e){return Et.has(e)?!0:Dt.some(t=>e.startsWith(t))}function Ot(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=We(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=Ot(e.command,o);t&&(o={...o,cwd:t})}let t=kt(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){if(e.pre){let n=G(e.pre,t);if(n.action!==`allow`)return n}let n=G(e.main,t);if(n.action!==`allow`)return n;if(e.post){let n=G(e.post,t);if(n.action!==`allow`)return n}return`allow`}function kt(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(W(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=Ke(i,e);if(!s.safe)return{action:`passthrough`,rule:null,reason:s.reason};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(W(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 At(i,t);if(l===`npm`)return Mt(i);if(l===`bash`||l===`sh`||l===`zsh`)return Ft(i,t);if(l===`xargs`)return Lt(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 gt)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 V)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`allow`,rule:e.name,reason:null};return{action:`passthrough`,rule:null,reason:`no rule matched: ${l}`}}function At(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 Nt(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(`-`)&&!z.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(!z.has(r))l=r;else{for(let n of V)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 jt(l,t);if(s){let e=bt(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=H(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 jt(e,t){let n=yt(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 Mt(e){for(let t of V)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:ct(e)}}function Nt(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(`-`)&&!z.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(z.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=vt(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 Pt=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function Ft(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(`-`)||Pt.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`]),It=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function Lt(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(It.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 Y=new Set([`Read`,`Glob`,`Grep`]),X=new Set([`Write`,`Edit`]),Rt=[{name:`env-file-read`,tools:Y,action:`deny`,when:e=>I(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=>I(e),reason:`Don't write to .env files. These contain secrets.`},{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.`}],zt=[{name:`cwd-read`,tools:Y,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)}}],Bt=new Set([`Read`,`Write`,`Edit`]),Vt=new Set([`Glob`,`Grep`]);function Ht(e,t,n){return Bt.has(e)?Re(t)?.file_path??null:Vt.has(e)?ze(t)?.path??n:null}function Ut(e,t,n){let r=Ht(e,t,n.cwd);if(r===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of Rt)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 Z=await Ne();Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);let Q,$;if(Z.tool_name===`Bash`){let e=Le(Z.tool_input);e||process.exit(0),Q=G(e.command,{cwd:Z.cwd,depth:0}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=Ut(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}Ue({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?N(`allow`):Q.action===`deny`&&N(`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- **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")];
2709
+ return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.2\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 h=Symbol(`evaluating`);function g(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==h)return r===void 0&&(r=h,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const _=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function v(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function re(e){if(v(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(v(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function ie(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function y(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 ae(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function b(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 x(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function S(e){return typeof e==`string`?e:e?.message}function C(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=S(e.inst?._zod.def?.error?.(e))??S(t?.error?.(e))??S(n.customError?.(e))??S(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const w=(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})},oe=d(`$ZodError`,w),T=d(`$ZodError`,w,{Parent:Error}),se=(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=>C(e,a,m())));throw _(t,i?.callee),t}return o.value})(T),ce=(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=>C(e,a,m())));throw _(t,i?.callee),t}return o.value})(T),le=(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??oe)(a.issues.map(e=>C(e,i,m())))}:{success:!0,data:a.value}})(T),ue=(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=>C(e,i,m())))}:{success:!0,data:a.value}})(T),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=b(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||=b(e,t))});else{if(e.issues.length===t)continue;r||=b(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(b(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)}}g(e,`~standard`,()=>({validate:t=>{try{let n=le(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return ue(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 D(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...x(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function ge(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=ae(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function _e(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 ve=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(()=>ge(t));g(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=v,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?_e(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),ye=d(`$ZodRecord`,(e,t)=>{E.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!re(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(...x(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...x(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=>C(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(...x(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...x(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function be(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const xe=d(`$ZodOptional`,(e,t)=>{E.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,g(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),g(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=>be(t,e.value)):be(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var Se,Ce=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 we(){return new Ce}(Se=globalThis).__zod_globalRegistry??(Se.__zod_globalRegistry=we()),globalThis.__zod_globalRegistry;function Te(e,t){return new e({type:`string`,...y(t)})}function Ee(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)=>se(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>le(e,t,n),e.parseAsync=async(t,n)=>ce(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>ue(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)=>ie(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),De=d(`ZodMiniString`,(e,t)=>{me.init(e,t),O.init(e,t)});function k(e){return Te(De,e)}const Oe=d(`ZodMiniUnknown`,(e,t)=>{he.init(e,t),O.init(e,t)});function A(){return Ee(Oe)}const ke=d(`ZodMiniObject`,(e,t)=>{ve.init(e,t),O.init(e,t),g(e,`shape`,()=>t.shape)});function j(e,t){return new ke({type:`object`,shape:e??{},...y(t)})}const Ae=d(`ZodMiniRecord`,(e,t)=>{ye.init(e,t),O.init(e,t)});function M(e,t,n){return new Ae({type:`record`,keyType:e,valueType:t,...y(n)})}const je=d(`ZodMiniOptional`,(e,t)=>{xe.init(e,t),O.init(e,t)});function N(e){return new je({type:`optional`,innerType:e})}const Me=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 Ne(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Me.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Pe=j({command:k(),description:N(k())}),Fe=j({file_path:k()}),Ie=j({pattern:k(),path:N(k())});function Le(e){let t=Pe.safeParse(e);return t.success?t.data:null}function Re(e){let t=Fe.safeParse(e);return t.success?t.data:null}function ze(e){let t=Ie.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 Be(){let e=a.dirname(l(import.meta.url));return a.join(e,`bst-hook.audit.local.jsonl`)}function Ve(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let He=!1;function Ue(t){try{let n=Be();He||=(Ve(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 We(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 Ge=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Ke(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(`>(`)&&!Ge.has(t))return{safe:!1,reason:`process substitution`}}if(!n)for(let t=0;t<e.length;t++){let n=e[t];if(!Ge.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 qe=[`.sample`,`.example`,`.template`],Je=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function Ye(e){return qe.some(t=>e.endsWith(t))}function L(e){return Ye(a.basename(e))?!1:Je.test(e)}function Xe(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&L(e))}const Ze=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`]),Qe=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),$e=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),et=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(et.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function tt(e){for(let t of e.slice(1))if($e.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 Ze.has(t)}function nt(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 Qe.has(t)}const rt=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:rt.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(`.`)),it=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function at(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!B.has(e))){n=e;break}return n?it.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const ot=new Set([`view`,`info`,`show`]);function st(e){let t=e[1];return t!==void 0&&ot.has(t)}function ct(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 lt=new Set([`-exec`,`-execdir`,`-ok`,`-okdir`,`-delete`,`-fprint`,`-fprint0`,`-fprintf`,`-fls`]);function ut(e){return!e.some(e=>lt.has(e))}function dt(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function ft(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 pt(e){return e.some(e=>e===`777`||e===`0777`)}function mt(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 ht(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 gt=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>ft(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=>mt(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>ht(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>nt(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=>pt(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=>Xe(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=>st(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=>ut(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>dt(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>tt(e)},{name:`git-safe-write`,match:/^git$/,action:`allow`,when:e=>z(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>at(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)}],_t=j({scripts:M(k(),A())});function U(e,t){let n=Tt(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 vt(e,t,n){let r=xt(t,n);if(!r)return null;let i=U(e,r);return i?{...i,pkgDir:r}:null}function yt(e,t){let n=W(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 bt(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 W(e){let t=St(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 xt(e,t){let n=W(t);if(!n)return null;for(let t of n){let n=wt(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function St(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 Ct=j({name:k()});function wt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=Ct.safeParse(JSON.parse(t));return r.success?r.data.name:null}catch{return null}}function Tt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=_t.safeParse(JSON.parse(t));return r.success?r.data.scripts:null}catch{return null}}function Et(e){try{return u(`git`,[`ls-files`,`--error-unmatch`,`package.json`],{cwd:e,stdio:`ignore`}),u(`git`,[`diff`,`--quiet`,`HEAD`,`--`,`package.json`],{cwd:e,stdio:`ignore`}),!0}catch{return!1}}const Dt=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`]),Ot=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function G(e){return Dt.has(e)?!0:Ot.some(t=>e.startsWith(t))}function kt(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=We(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=kt(e.command,o);t&&(o={...o,cwd:t})}let t=At(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??Et(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 At(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(G(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=Ke(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(G(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 jt(i,t);if(l===`npm`)return Nt(i);if(l===`bash`||l===`sh`||l===`zsh`)return It(i,t);if(l===`xargs`)return Rt(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 gt)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};return t.insideResolvedScript?{action:`allow`,rule:`resolved-script`,reason:null}:{action:`passthrough`,rule:null,reason:`no rule matched: ${l}`}}function jt(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 Pt(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`}: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(!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 Mt(l,t);if(s){let e=bt(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=U(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 Mt(e,t){let n=yt(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 Nt(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:ct(e)}}function Pt(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`}: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(B.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=vt(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 Ft=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function It(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(`-`)||Ft.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`]),Lt=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function Rt(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(Lt.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 zt=new Set([`Read`,`Glob`,`Grep`]),X=new Set([`Write`,`Edit`]),Bt=[{name:`env-file-read`,tools:zt,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:X,action:`deny`,when:e=>L(e),reason:`Don't write to .env files. These contain secrets.`},{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.`}],Vt=[{name:`cwd-read`,tools:zt,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)}}],Ht=new Set([`Read`,`Write`,`Edit`]),Ut=new Set([`Glob`,`Grep`]);function Wt(e,t,n){return Ht.has(e)?Re(t)?.file_path??null:Ut.has(e)?ze(t)?.path??n:null}function Gt(e,t,n){let r=Wt(e,t,n.cwd);if(r===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of Bt)if(t.tools.has(e)&&t.when(r,n))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of Vt)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 Ne();Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);let Q,$;if(Z.tool_name===`Bash`){let e=Le(Z.tool_input);e||process.exit(0),Q=K(e.command,{cwd:Z.cwd,depth:0}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=Gt(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}Ue({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- **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")];
2710
2710
  }
2711
2711
  //#endregion
2712
2712
  //#region src/generators/claude-settings.ts
@@ -3332,7 +3332,7 @@ async function runGenerators(ctx) {
3332
3332
  function generateMigratePrompt(results, config, detected) {
3333
3333
  const sections = [];
3334
3334
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3335
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.44.0 repo:sync\` on ${timestamp}_\n`);
3335
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.45.0 repo:sync\` on ${timestamp}_\n`);
3336
3336
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
3337
3337
  if (changed.length > 0) {
3338
3338
  const items = changed.map((r) => {
@@ -4239,7 +4239,185 @@ async function runVersionMode(executor, config) {
4239
4239
  };
4240
4240
  }
4241
4241
  //#endregion
4242
+ //#region src/release/connection.ts
4243
+ const RepositorySchema = z.union([z.string(), z.object({ url: z.string() })]);
4244
+ /**
4245
+ * Resolve the hosting platform and connection details.
4246
+ *
4247
+ * Priority:
4248
+ * 1. Environment variables (FORGEJO_SERVER_URL, FORGEJO_REPOSITORY, RELEASE_TOKEN)
4249
+ * 2. `repository` field in package.json (server URL and owner/repo parsed from the URL)
4250
+ *
4251
+ * For Forgejo, a token is always required. Accepts RELEASE_TOKEN (preferred) or
4252
+ * FORGEJO_TOKEN (legacy, but conflicts with Forgejo Actions' internal auth).
4253
+ * If the repository URL hostname is `github.com`, returns `{ type: "github" }`.
4254
+ */
4255
+ function resolveConnection(cwd) {
4256
+ const serverUrl = process.env["FORGEJO_SERVER_URL"];
4257
+ const repository = process.env["FORGEJO_REPOSITORY"];
4258
+ const token = process.env["RELEASE_TOKEN"] ?? process.env["FORGEJO_TOKEN"];
4259
+ if (serverUrl && repository && token) return {
4260
+ type: "forgejo",
4261
+ conn: {
4262
+ serverUrl,
4263
+ repository,
4264
+ token
4265
+ }
4266
+ };
4267
+ const parsed = parseRepositoryUrl(cwd);
4268
+ if (parsed === null) {
4269
+ if (serverUrl) {
4270
+ if (!repository) throw new FatalError("FORGEJO_REPOSITORY environment variable is required");
4271
+ if (!token) throw new FatalError("RELEASE_TOKEN environment variable is required");
4272
+ }
4273
+ return { type: "github" };
4274
+ }
4275
+ if (parsed.hostname === "github.com") return { type: "github" };
4276
+ const resolvedToken = token;
4277
+ if (!resolvedToken) throw new FatalError("RELEASE_TOKEN environment variable is required (server URL and repository were resolved from package.json)");
4278
+ return {
4279
+ type: "forgejo",
4280
+ conn: {
4281
+ serverUrl: serverUrl ?? `${parsed.protocol}//${parsed.hostname}`,
4282
+ repository: repository ?? parsed.repository,
4283
+ token: resolvedToken
4284
+ }
4285
+ };
4286
+ }
4287
+ function parseRepositoryUrl(cwd) {
4288
+ const pkgPath = path.join(cwd, "package.json");
4289
+ let raw;
4290
+ try {
4291
+ raw = readFileSync(pkgPath, "utf-8");
4292
+ } catch {
4293
+ return null;
4294
+ }
4295
+ const pkg = z.object({ repository: RepositorySchema.optional() }).safeParse(JSON.parse(raw));
4296
+ if (!pkg.success) return null;
4297
+ const repo = pkg.data.repository;
4298
+ if (!repo) return null;
4299
+ return parseGitUrl(typeof repo === "string" ? repo : repo.url);
4300
+ }
4301
+ function parseGitUrl(urlStr) {
4302
+ try {
4303
+ const url = new URL(urlStr);
4304
+ const pathname = url.pathname.replace(/\.git$/, "").replace(/^\//, "");
4305
+ if (!pathname.includes("/")) return null;
4306
+ return {
4307
+ protocol: url.protocol,
4308
+ hostname: url.hostname,
4309
+ repository: pathname
4310
+ };
4311
+ } catch {
4312
+ return null;
4313
+ }
4314
+ }
4315
+ /**
4316
+ * Configure the git remote origin to use a Forgejo token for push auth.
4317
+ *
4318
+ * This is necessary because `actions/checkout` configures git with the automatic
4319
+ * GITHUB_TOKEN, which may not have push permissions. Using RELEASE_TOKEN ensures
4320
+ * pushes use the explicitly provided token.
4321
+ */
4322
+ function configureGitAuth(executor, conn, cwd) {
4323
+ const host = conn.serverUrl.replace(/^https?:\/\//, "");
4324
+ const authUrl = `https://x-access-token:${conn.token}@${host}/${conn.repository}`;
4325
+ executor.exec("git", [
4326
+ "remote",
4327
+ "set-url",
4328
+ "origin",
4329
+ authUrl
4330
+ ], { cwd });
4331
+ }
4332
+ /** Resolve the CI bot actor name for the given platform. */
4333
+ function ciActorName(platform) {
4334
+ return platform === "github" ? "github-actions[bot]" : "forgejo-actions[bot]";
4335
+ }
4336
+ /** Resolve the CI bot email for the given platform. */
4337
+ function ciActorEmail(platform) {
4338
+ const name = ciActorName(platform);
4339
+ return platform === "github" ? `${name}@users.noreply.github.com` : `${name}@noreply.localhost`;
4340
+ }
4341
+ /** Configure git user.name and user.email for CI bot commits. */
4342
+ function configureGitIdentity(executor, platform, cwd) {
4343
+ executor.exec("git", [
4344
+ "config",
4345
+ "user.name",
4346
+ ciActorName(platform)
4347
+ ], { cwd });
4348
+ executor.exec("git", [
4349
+ "config",
4350
+ "user.email",
4351
+ ciActorEmail(platform)
4352
+ ], { cwd });
4353
+ }
4354
+ //#endregion
4242
4355
  //#region src/release/webhook.ts
4356
+ /** Build a webhook payload from changesets publish context. */
4357
+ function buildChangesetsPayload(options) {
4358
+ const tagName = options.tags.join(", ");
4359
+ const actor = options.actor ?? ciActorName(options.platform ?? "forgejo");
4360
+ return {
4361
+ action: "published",
4362
+ release: {
4363
+ tag_name: tagName,
4364
+ name: tagName,
4365
+ draft: false,
4366
+ prerelease: false,
4367
+ author: { login: actor }
4368
+ },
4369
+ repository: {
4370
+ full_name: options.repository,
4371
+ html_url: `${options.serverUrl}/${options.repository}`
4372
+ },
4373
+ sender: { login: actor }
4374
+ };
4375
+ }
4376
+ /** Build a webhook payload from simple release context. */
4377
+ function buildSimplePayload(options) {
4378
+ const repository = options.repository ?? "";
4379
+ const serverUrl = options.serverUrl ?? "https://github.com";
4380
+ const actor = options.actor ?? ciActorName(options.platform ?? "github");
4381
+ return {
4382
+ action: "published",
4383
+ release: {
4384
+ tag_name: options.tag,
4385
+ name: options.tag,
4386
+ draft: false,
4387
+ prerelease: options.prerelease ?? false,
4388
+ author: { login: actor }
4389
+ },
4390
+ repository: {
4391
+ full_name: repository,
4392
+ html_url: repository ? `${serverUrl}/${repository}` : ""
4393
+ },
4394
+ sender: { login: actor }
4395
+ };
4396
+ }
4397
+ /** Build a webhook payload from GitHub Actions environment variables. */
4398
+ function buildPayloadFromEnv() {
4399
+ const repository = process.env["GITHUB_REPOSITORY"];
4400
+ const ref = process.env["GITHUB_REF"];
4401
+ const serverUrl = process.env["GITHUB_SERVER_URL"] ?? "https://github.com";
4402
+ const actor = process.env["GITHUB_ACTOR"] ?? "github-actions[bot]";
4403
+ if (!repository || !ref) return void 0;
4404
+ const tagName = ref.startsWith("refs/tags/") ? ref.slice(10) : ref;
4405
+ return {
4406
+ action: "published",
4407
+ release: {
4408
+ tag_name: tagName,
4409
+ name: tagName,
4410
+ draft: false,
4411
+ prerelease: false,
4412
+ author: { login: actor }
4413
+ },
4414
+ repository: {
4415
+ full_name: repository,
4416
+ html_url: `${serverUrl}/${repository}`
4417
+ },
4418
+ sender: { login: actor }
4419
+ };
4420
+ }
4243
4421
  /**
4244
4422
  * Compute the HMAC-SHA256 hex digest of data using the given secret.
4245
4423
  * Returns the signature in GitHub-style format: `sha256=<hex>`.
@@ -4252,14 +4430,15 @@ function computeSignature(secret, data) {
4252
4430
  /**
4253
4431
  * POST to a webhook URL with GitHub-style HMAC-SHA256 authentication.
4254
4432
  *
4255
- * Sends an empty body. If a secret is provided, computes the HMAC-SHA256
4256
- * signature and includes it in the `X-Hub-Signature-256` header.
4433
+ * Sends a JSON-serialized release payload (or empty string if no payload).
4434
+ * If a secret is provided, computes the HMAC-SHA256 signature of the body
4435
+ * and includes it in the `X-Hub-Signature-256` header.
4257
4436
  *
4258
4437
  * Never throws — returns a result object indicating success or failure.
4259
4438
  */
4260
4439
  async function sendWebhook(options) {
4261
- const { url, secret, fetcher = globalThis.fetch } = options;
4262
- const body = "";
4440
+ const { url, secret, payload, fetcher = globalThis.fetch } = options;
4441
+ const body = payload ? JSON.stringify(payload) : "";
4263
4442
  const headers = { "Content-Type": "application/json" };
4264
4443
  if (secret) headers["X-Hub-Signature-256"] = computeSignature(secret, body);
4265
4444
  try {
@@ -4288,10 +4467,11 @@ async function sendWebhook(options) {
4288
4467
  * Send a webhook and log the result. Intended for CLI usage.
4289
4468
  * Never throws.
4290
4469
  */
4291
- async function sendWebhookWithLogging(url, secret) {
4470
+ async function sendWebhookWithLogging(url, secret, payload) {
4292
4471
  const result = await sendWebhook({
4293
4472
  url,
4294
- secret
4473
+ secret,
4474
+ payload
4295
4475
  });
4296
4476
  if (result.ok) log.info("Post-release hook completed successfully");
4297
4477
  else log.warn(`Post-release hook failed: ${result.error ?? "unknown error"}`);
@@ -4321,7 +4501,7 @@ function loadHookCommand(executor, cwd) {
4321
4501
  *
4322
4502
  * Failures are logged as warnings — they never fail the release.
4323
4503
  */
4324
- async function runPostReleaseHook(executor, config) {
4504
+ async function runPostReleaseHook(executor, config, payload) {
4325
4505
  if (config.dryRun) {
4326
4506
  debug(config, "Skipping post-release hook (dry-run)");
4327
4507
  return;
@@ -4342,6 +4522,7 @@ async function runPostReleaseHook(executor, config) {
4342
4522
  const result = await sendWebhook({
4343
4523
  url: hookUrl,
4344
4524
  secret: hookSecret || void 0,
4525
+ payload,
4345
4526
  fetcher: executor.fetch.bind(executor)
4346
4527
  });
4347
4528
  if (result.ok) log.info("Post-release hook completed successfully");
@@ -4456,120 +4637,17 @@ async function runPublishMode(executor, config) {
4456
4637
  if (forcePushResult.exitCode !== 0) log.warn(`Warning: Failed to push sliding tags: ${forcePushResult.stderr || forcePushResult.stdout}`);
4457
4638
  else log.info(`Created sliding tags: ${slidingTags.join(", ")}`);
4458
4639
  }
4459
- await runPostReleaseHook(executor, config);
4640
+ await runPostReleaseHook(executor, config, buildChangesetsPayload({
4641
+ tags: allTags,
4642
+ repository: config.repository,
4643
+ serverUrl: config.serverUrl
4644
+ }));
4460
4645
  return {
4461
4646
  mode: "publish",
4462
4647
  tags: allTags
4463
4648
  };
4464
4649
  }
4465
4650
  //#endregion
4466
- //#region src/release/connection.ts
4467
- const RepositorySchema = z.union([z.string(), z.object({ url: z.string() })]);
4468
- /**
4469
- * Resolve the hosting platform and connection details.
4470
- *
4471
- * Priority:
4472
- * 1. Environment variables (FORGEJO_SERVER_URL, FORGEJO_REPOSITORY, RELEASE_TOKEN)
4473
- * 2. `repository` field in package.json (server URL and owner/repo parsed from the URL)
4474
- *
4475
- * For Forgejo, a token is always required. Accepts RELEASE_TOKEN (preferred) or
4476
- * FORGEJO_TOKEN (legacy, but conflicts with Forgejo Actions' internal auth).
4477
- * If the repository URL hostname is `github.com`, returns `{ type: "github" }`.
4478
- */
4479
- function resolveConnection(cwd) {
4480
- const serverUrl = process.env["FORGEJO_SERVER_URL"];
4481
- const repository = process.env["FORGEJO_REPOSITORY"];
4482
- const token = process.env["RELEASE_TOKEN"] ?? process.env["FORGEJO_TOKEN"];
4483
- if (serverUrl && repository && token) return {
4484
- type: "forgejo",
4485
- conn: {
4486
- serverUrl,
4487
- repository,
4488
- token
4489
- }
4490
- };
4491
- const parsed = parseRepositoryUrl(cwd);
4492
- if (parsed === null) {
4493
- if (serverUrl) {
4494
- if (!repository) throw new FatalError("FORGEJO_REPOSITORY environment variable is required");
4495
- if (!token) throw new FatalError("RELEASE_TOKEN environment variable is required");
4496
- }
4497
- return { type: "github" };
4498
- }
4499
- if (parsed.hostname === "github.com") return { type: "github" };
4500
- const resolvedToken = token;
4501
- if (!resolvedToken) throw new FatalError("RELEASE_TOKEN environment variable is required (server URL and repository were resolved from package.json)");
4502
- return {
4503
- type: "forgejo",
4504
- conn: {
4505
- serverUrl: serverUrl ?? `${parsed.protocol}//${parsed.hostname}`,
4506
- repository: repository ?? parsed.repository,
4507
- token: resolvedToken
4508
- }
4509
- };
4510
- }
4511
- function parseRepositoryUrl(cwd) {
4512
- const pkgPath = path.join(cwd, "package.json");
4513
- let raw;
4514
- try {
4515
- raw = readFileSync(pkgPath, "utf-8");
4516
- } catch {
4517
- return null;
4518
- }
4519
- const pkg = z.object({ repository: RepositorySchema.optional() }).safeParse(JSON.parse(raw));
4520
- if (!pkg.success) return null;
4521
- const repo = pkg.data.repository;
4522
- if (!repo) return null;
4523
- return parseGitUrl(typeof repo === "string" ? repo : repo.url);
4524
- }
4525
- function parseGitUrl(urlStr) {
4526
- try {
4527
- const url = new URL(urlStr);
4528
- const pathname = url.pathname.replace(/\.git$/, "").replace(/^\//, "");
4529
- if (!pathname.includes("/")) return null;
4530
- return {
4531
- protocol: url.protocol,
4532
- hostname: url.hostname,
4533
- repository: pathname
4534
- };
4535
- } catch {
4536
- return null;
4537
- }
4538
- }
4539
- /**
4540
- * Configure the git remote origin to use a Forgejo token for push auth.
4541
- *
4542
- * This is necessary because `actions/checkout` configures git with the automatic
4543
- * GITHUB_TOKEN, which may not have push permissions. Using RELEASE_TOKEN ensures
4544
- * pushes use the explicitly provided token.
4545
- */
4546
- function configureGitAuth(executor, conn, cwd) {
4547
- const host = conn.serverUrl.replace(/^https?:\/\//, "");
4548
- const authUrl = `https://x-access-token:${conn.token}@${host}/${conn.repository}`;
4549
- executor.exec("git", [
4550
- "remote",
4551
- "set-url",
4552
- "origin",
4553
- authUrl
4554
- ], { cwd });
4555
- }
4556
- /** Configure git user.name and user.email for CI bot commits. */
4557
- function configureGitIdentity(executor, platform, cwd) {
4558
- const isGitHub = platform === "github";
4559
- const name = isGitHub ? "github-actions[bot]" : "forgejo-actions[bot]";
4560
- const email = isGitHub ? "github-actions[bot]@users.noreply.github.com" : "forgejo-actions[bot]@noreply.localhost";
4561
- executor.exec("git", [
4562
- "config",
4563
- "user.name",
4564
- name
4565
- ], { cwd });
4566
- executor.exec("git", [
4567
- "config",
4568
- "user.email",
4569
- email
4570
- ], { cwd });
4571
- }
4572
- //#endregion
4573
4651
  //#region src/commands/release-changesets.ts
4574
4652
  const releaseForgejoCommand = defineCommand({
4575
4653
  meta: {
@@ -4880,7 +4958,16 @@ async function runSimpleRelease(executor, config) {
4880
4958
  }
4881
4959
  let releaseCreated = false;
4882
4960
  if (!config.noRelease && config.platform) releaseCreated = await createPlatformRelease(executor, config, tag);
4883
- if (pushed) await runPostReleaseHook(executor, config);
4961
+ if (pushed) {
4962
+ const conn = config.platform?.type === "forgejo" ? config.platform.conn : void 0;
4963
+ await runPostReleaseHook(executor, config, buildSimplePayload({
4964
+ tag,
4965
+ repository: conn?.repository,
4966
+ serverUrl: conn?.serverUrl,
4967
+ prerelease: Boolean(config.prerelease),
4968
+ platform: config.platform?.type
4969
+ }));
4970
+ }
4884
4971
  return {
4885
4972
  version,
4886
4973
  tag,
@@ -6011,14 +6098,43 @@ const webhookSendCommand = defineCommand({
6011
6098
  name: "webhook:send",
6012
6099
  description: "Send a POST request to a webhook URL with GitHub-style HMAC-SHA256 authentication"
6013
6100
  },
6014
- args: { url: {
6015
- type: "positional",
6016
- description: "Webhook URL to POST to",
6017
- required: true
6018
- } },
6101
+ args: {
6102
+ url: {
6103
+ type: "positional",
6104
+ description: "Webhook URL to POST to",
6105
+ required: true
6106
+ },
6107
+ tag: {
6108
+ type: "string",
6109
+ description: "Release tag name (e.g. v1.2.3). Falls back to GITHUB_REF env var."
6110
+ },
6111
+ repository: {
6112
+ type: "string",
6113
+ description: "Repository in owner/repo format. Falls back to GITHUB_REPOSITORY env var."
6114
+ },
6115
+ "server-url": {
6116
+ type: "string",
6117
+ description: "Server URL (e.g. https://github.com). Falls back to GITHUB_SERVER_URL env var."
6118
+ },
6119
+ actor: {
6120
+ type: "string",
6121
+ description: "User or bot that triggered the release. Falls back to GITHUB_ACTOR env var."
6122
+ },
6123
+ prerelease: {
6124
+ type: "boolean",
6125
+ description: "Mark the release as a prerelease"
6126
+ }
6127
+ },
6019
6128
  async run({ args }) {
6020
6129
  const secret = process.env["WEBHOOK_SECRET"];
6021
- await sendWebhookWithLogging(args.url, secret || void 0);
6130
+ const payload = args.tag || args.repository ? buildSimplePayload({
6131
+ tag: args.tag ?? "",
6132
+ repository: args.repository ?? process.env["GITHUB_REPOSITORY"] ?? "",
6133
+ serverUrl: args["server-url"] ?? process.env["GITHUB_SERVER_URL"] ?? "https://github.com",
6134
+ actor: args.actor ?? process.env["GITHUB_ACTOR"] ?? "github-actions[bot]",
6135
+ prerelease: args.prerelease ?? false
6136
+ }) : buildPayloadFromEnv();
6137
+ await sendWebhookWithLogging(args.url, secret || void 0, payload);
6022
6138
  }
6023
6139
  });
6024
6140
  //#endregion
@@ -6026,7 +6142,7 @@ const webhookSendCommand = defineCommand({
6026
6142
  const main = defineCommand({
6027
6143
  meta: {
6028
6144
  name: "bst",
6029
- version: "0.44.0",
6145
+ version: "0.45.0",
6030
6146
  description: "Bootstrap and maintain standardized TypeScript project tooling"
6031
6147
  },
6032
6148
  subCommands: {
@@ -6046,7 +6162,7 @@ const main = defineCommand({
6046
6162
  "webhook:send": webhookSendCommand
6047
6163
  }
6048
6164
  });
6049
- console.log(`@bensandee/tooling v0.44.0`);
6165
+ console.log(`@bensandee/tooling v0.45.0`);
6050
6166
  async function run() {
6051
6167
  await runMain(main);
6052
6168
  process.exit(process.exitCode ?? 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.44.0",
3
+ "version": "0.45.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/config": "0.10.1",
58
- "@bensandee/claude-hooks": "0.6.1"
57
+ "@bensandee/claude-hooks": "0.6.2",
58
+ "@bensandee/config": "0.10.1"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "tsdown",