@bensandee/tooling 0.38.0 → 0.40.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 (2) hide show
  1. package/dist/bin.mjs +276 -121
  2. package/package.json +3 -2
package/dist/bin.mjs CHANGED
@@ -1563,9 +1563,9 @@ async function generatePackageJson(ctx) {
1563
1563
  }
1564
1564
  const devDeps = { ...ROOT_DEV_DEPS };
1565
1565
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1566
- devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.2";
1567
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.38.0";
1568
- if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.10.0";
1566
+ devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.0";
1567
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.40.0";
1568
+ if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
1569
1569
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
1570
1570
  "@changesets/cli": "2.30.0",
1571
1571
  "@release-it/bumper": "7.0.5",
@@ -1932,6 +1932,7 @@ import { defineConfig } from "oxlint";
1932
1932
 
1933
1933
  export default defineConfig({
1934
1934
  extends: [recommended],
1935
+ ignorePatterns: [".claude/**"],
1935
1936
  });
1936
1937
  `;
1937
1938
  const CONFIG_PRESET_ONLY = `import { presetRules } from "@bensandee/config/oxlint";
@@ -1939,6 +1940,7 @@ import { defineConfig } from "oxlint";
1939
1940
 
1940
1941
  export default defineConfig({
1941
1942
  rules: presetRules,
1943
+ ignorePatterns: [".claude/**"],
1942
1944
  });
1943
1945
  `;
1944
1946
  async function generateOxlint(ctx) {
@@ -1966,7 +1968,14 @@ async function generateOxlint(ctx) {
1966
1968
  }
1967
1969
  //#endregion
1968
1970
  //#region src/generators/formatter.ts
1969
- const OXFMT_CONFIG = `{}\n`;
1971
+ /** Ignore patterns that must always be present in .oxfmtrc.json. */
1972
+ const BASE_IGNORE_PATTERNS = [".claude"];
1973
+ /** Extra ignore patterns when a release strategy generates CHANGELOGs. */
1974
+ const RELEASE_IGNORE_PATTERNS = ["**/CHANGELOG.md"];
1975
+ function requiredIgnorePatterns(releaseStrategy) {
1976
+ if (releaseStrategy === "none") return BASE_IGNORE_PATTERNS;
1977
+ return [...BASE_IGNORE_PATTERNS, ...RELEASE_IGNORE_PATTERNS];
1978
+ }
1970
1979
  const PRETTIER_CONFIG = `{
1971
1980
  "semi": true,
1972
1981
  "singleQuote": false,
@@ -1981,16 +1990,55 @@ async function generateFormatter(ctx) {
1981
1990
  }
1982
1991
  async function generateOxfmt(ctx) {
1983
1992
  const filePath = ".oxfmtrc.json";
1984
- if (ctx.exists(filePath)) return {
1993
+ const required = requiredIgnorePatterns(ctx.config.releaseStrategy);
1994
+ const existing = ctx.read(filePath);
1995
+ if (!existing) {
1996
+ const config = { ignorePatterns: required };
1997
+ ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
1998
+ return {
1999
+ filePath,
2000
+ action: "created",
2001
+ description: "Generated .oxfmtrc.json"
2002
+ };
2003
+ }
2004
+ const OxfmtConfigSchema = z.record(z.string(), z.unknown());
2005
+ let jsonValue;
2006
+ try {
2007
+ jsonValue = JSON.parse(existing);
2008
+ } catch {
2009
+ const config = { ignorePatterns: required };
2010
+ ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
2011
+ return {
2012
+ filePath,
2013
+ action: "updated",
2014
+ description: "Replaced malformed .oxfmtrc.json"
2015
+ };
2016
+ }
2017
+ const parseResult = OxfmtConfigSchema.safeParse(jsonValue);
2018
+ if (!parseResult.success) {
2019
+ const config = { ignorePatterns: required };
2020
+ ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
2021
+ return {
2022
+ filePath,
2023
+ action: "updated",
2024
+ description: "Replaced malformed .oxfmtrc.json"
2025
+ };
2026
+ }
2027
+ const parsed = parseResult.data;
2028
+ const ignorePatterns = Array.isArray(parsed["ignorePatterns"]) ? parsed["ignorePatterns"] : [];
2029
+ const existingSet = new Set(ignorePatterns.filter((p) => typeof p === "string"));
2030
+ const missing = required.filter((p) => !existingSet.has(p));
2031
+ if (missing.length === 0) return {
1985
2032
  filePath,
1986
2033
  action: "skipped",
1987
- description: "Existing oxfmt config preserved"
2034
+ description: "Existing oxfmt config already has required ignorePatterns"
1988
2035
  };
1989
- ctx.write(filePath, OXFMT_CONFIG);
2036
+ parsed["ignorePatterns"] = [...ignorePatterns, ...missing];
2037
+ ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
1990
2038
  return {
1991
2039
  filePath,
1992
- action: "created",
1993
- description: "Generated .oxfmtrc.json"
2040
+ action: "updated",
2041
+ description: `Added ignorePatterns: ${missing.join(", ")}`
1994
2042
  };
1995
2043
  }
1996
2044
  async function generatePrettier(ctx) {
@@ -2055,7 +2103,11 @@ const REQUIRED_ENTRIES = [
2055
2103
  "!.env.example"
2056
2104
  ];
2057
2105
  /** Tooling-specific entries added during init/update but not required for repo:sync --check. */
2058
- const OPTIONAL_ENTRIES = [".tooling-migrate.md", ".tooling-archived/"];
2106
+ const OPTIONAL_ENTRIES = [
2107
+ ".tooling-migrate.md",
2108
+ ".tooling-archived/",
2109
+ ".claude/hooks/bst-hook.audit.local.jsonl"
2110
+ ];
2059
2111
  const ALL_ENTRIES = [...REQUIRED_ENTRIES, ...OPTIONAL_ENTRIES];
2060
2112
  /** Normalize a gitignore entry for comparison: strip leading `/` and trailing `/`. */
2061
2113
  function normalizeEntry(entry) {
@@ -2214,12 +2266,13 @@ const KNIP_CONFIG_SINGLE = `import type { KnipConfig } from "knip";
2214
2266
  export default {
2215
2267
  entry: ["src/index.ts"],
2216
2268
  project: ["src/**/*.ts"],
2217
- ignore: ["dist/**"],
2269
+ ignore: ["dist/**", ".claude/hooks/**"],
2218
2270
  } satisfies KnipConfig;
2219
2271
  `;
2220
2272
  const KNIP_CONFIG_MONOREPO = `import type { KnipConfig } from "knip";
2221
2273
 
2222
2274
  export default {
2275
+ ignore: [".claude/hooks/**"],
2223
2276
  workspaces: {
2224
2277
  ".": {
2225
2278
  entry: [],
@@ -2350,7 +2403,33 @@ async function generatePnpmWorkspace(ctx) {
2350
2403
  };
2351
2404
  }
2352
2405
  //#endregion
2406
+ //#region src/generators/claude-hooks.ts
2407
+ const HOOK_PATH = ".claude/hooks/bst-hook.mjs";
2408
+ async function generateClaudeHooks(ctx) {
2409
+ const content = "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.2.0\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{readFileSync as t,readdirSync as n,statSync as r}from\"node:fs\";import*as i from\"node:path\";import{join as a,resolve as o}from\"node:path\";import{fileURLToPath as s}from\"node:url\";Object.freeze({status:`aborted`});function c(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 l=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const u={};function d(e){return e&&Object.assign(u,e),u}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 f=Symbol(`evaluating`);function p(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==f)return r===void 0&&(r=f,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const m=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function h(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function re(e){if(h(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(h(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function ie(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function g(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function _(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})},ae=c(`$ZodError`,S),C=c(`$ZodError`,S,{Parent:Error}),w=(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 l;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,d())));throw m(t,i?.callee),t}return o.value})(C),oe=(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,d())));throw m(t,i?.callee),t}return o.value})(C),T=(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 l;return a.issues.length?{success:!1,error:new(e??ae)(a.issues.map(e=>x(e,i,d())))}:{success:!0,data:a.value}})(C),E=(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,d())))}:{success:!0,data:a.value}})(C),se=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},ce=/^-?\\d+(?:\\.\\d+)?$/,le={major:4,minor:3,patch:6},D=c(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=le;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 l;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 l;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 l;return o.then(e=>t(e,r,a))}return t(o,r,a)}}p(e,`~standard`,()=>({validate:t=>{try{let n=T(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return E(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),ue=c(`$ZodString`,(e,t)=>{D.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??se(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}}),de=c(`$ZodUnknown`,(e,t)=>{D.init(e,t),e._zod.parse=e=>e});function O(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 fe(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=_(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function pe(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=>O(e,n,i,t,u))):O(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 me=c(`$ZodObject`,(e,t)=>{if(D.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(()=>fe(t));p(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=h,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>O(n,t,e,s,r))):O(i,t,e,s,r)}return i?pe(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),he=c(`$ZodRecord`,(e,t)=>{D.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(...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`&&ce.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,d())),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 k(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const ge=c(`$ZodOptional`,(e,t)=>{D.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,p(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),p(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=>k(t,e.value)):k(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var A,_e=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 j(){return new _e}(A=globalThis).__zod_globalRegistry??(A.__zod_globalRegistry=j()),globalThis.__zod_globalRegistry;function M(e,t){return new e({type:`string`,...g(t)})}function N(e){return new e({type:`unknown`})}const P=c(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);D.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>w(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>T(e,t,n),e.parseAsync=async(t,n)=>oe(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>E(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)}),F=c(`ZodMiniString`,(e,t)=>{ue.init(e,t),P.init(e,t)});function I(e){return M(F,e)}const ve=c(`ZodMiniUnknown`,(e,t)=>{de.init(e,t),P.init(e,t)});function ye(){return N(ve)}const be=c(`ZodMiniObject`,(e,t)=>{me.init(e,t),P.init(e,t),p(e,`shape`,()=>t.shape)});function L(e,t){return new be({type:`object`,shape:e??{},...g(t)})}const xe=c(`ZodMiniRecord`,(e,t)=>{he.init(e,t),P.init(e,t)});function Se(e,t,n){return new xe({type:`record`,keyType:e,valueType:t,...g(n)})}const Ce=c(`ZodMiniOptional`,(e,t)=>{ge.init(e,t),P.init(e,t)});function we(e){return new Ce({type:`optional`,innerType:e})}const Te=L({session_id:I(),transcript_path:I(),cwd:I(),permission_mode:I(),hook_event_name:I(),tool_name:I(),tool_input:L({command:I(),description:we(I())})});async function Ee(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Te.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}function R(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function De(){let e=i.dirname(s(import.meta.url));return i.join(e,`bst-hook.audit.local.jsonl`)}function Oe(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let ke=!1;function Ae(t){try{let n=De();ke||=(Oe(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 z(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function je(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){B(t,n,`&&`),n=``,r+=2;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){B(t,n,`||`),n=``,r+=2;continue}if(o===`|`){B(t,n,`|`),n=``,r++;continue}if(o===`;`){B(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function B(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const V=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Me(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(`>(`)&&!V.has(t))return{safe:!1,reason:`process substitution`}}for(let t=0;t<e.length;t++){let n=e[t];if(!V.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 Ne=new Set([`status`,`log`,`diff`,`show`,`branch`,`remote`,`fetch`,`rev-parse`]),Pe=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),Fe=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),Ie=new Set([`-C`,`-c`,`--git-dir`,`--work-tree`]);function H(e){let t=e.slice(1);for(let e=0;e<t.length;e++){let n=t[e];if(Ie.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function Le(e){for(let t of e.slice(1))if(Fe.has(t)||t.startsWith(`--exec-path=`)||t.startsWith(`--pager=`)||t.startsWith(`--upload-pack=`)||t.startsWith(`--receive-pack=`))return!1;let t=H(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===`list`||t===void 0}return Ne.has(t)}function Re(e){let t=H(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];if(t===`list`||t===void 0)return!1}return Pe.has(t)}const U=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(`.`)),ze=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function Be(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!U.has(e))){n=e;break}return n?ze.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const Ve=new Set([`view`,`info`,`show`]);function He(e){let t=e[1];return t!==void 0&&Ve.has(t)}function Ue(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 W(e,t){for(let n of e.slice(1)){if(n.startsWith(`-`))continue;if(i.basename(n).startsWith(`.`)||n.startsWith(`~/`)||n===`~`)return!1;let e=i.resolve(t.cwd,n);if(e!==t.cwd&&!e.startsWith(t.cwd+i.sep))return!1}return!0}const We=new Set([`-exec`,`-execdir`,`-ok`,`-delete`]);function Ge(e){return!e.some(e=>We.has(e))}function Ke(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function qe(e){let t=e.findIndex(e=>i.basename(e)===`rm`);if(t===-1)return!1;let n=e.slice(t+1),r=n.filter(e=>e.startsWith(`-`)&&!e.startsWith(`--`)),a=n.filter(e=>e.startsWith(`--`)),o=r.some(e=>/[rR]/.test(e)),s=a.includes(`--recursive`);return o||s}function Je(e){return e.some(e=>e===`777`||e===`0777`)}const Ye=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function G(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&Ye.test(e))}function Xe(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 Ze(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 Qe=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>qe(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=>Xe(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>Ze(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>Re(e),reason:`Git write operations (push, add, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.`},{name:`chmod-777`,match:/^chmod$/,action:`deny`,when:e=>Je(e),reason:`Don't use 777 permissions. Use more restrictive permissions.`},{name:`env-file-cat`,match:/^cat$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-head`,match:/^head$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-tail`,match:/^tail$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-less`,match:/^less$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-more`,match:/^more$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-grep`,match:/^grep$/,action:`deny`,when:e=>G(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.\"}],K=[{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=>He(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=>Ge(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>Ke(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>Le(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>Be(e)},{name:`cp`,match:/^cp$/,action:`allow`,when:(e,t)=>W(e,t)},{name:`mkdir`,match:/^mkdir$/,action:`allow`,when:(e,t)=>W(e,t)},{name:`touch`,match:/^touch$/,action:`allow`,when:(e,t)=>W(e,t)}],$e=L({scripts:Se(I(),ye())});function q(e,t){let n=Y(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 et(e,t,n){let r=nt(t,n);if(!r)return null;let i=q(e,r);return i?{...i,pkgDir:r}:null}function tt(e,t){let n=J(t);if(!n)return null;let r=[];for(let t of n){let n=q(e,t);n&&r.push({...n,pkgDir:t})}return r}function J(e){let t=rt(e);if(!t)return null;let i=[];for(let s of t){if(!s.endsWith(`/*`))continue;let t=o(e,s.slice(0,-2)),c;try{c=n(t)}catch{continue}for(let e of c){let n=a(t,e);try{if(!r(n).isDirectory())continue}catch{continue}i.push(n)}}return i.length>0?i:null}function nt(e,t){let n=J(t);if(!n)return null;for(let t of n){let n=at(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function rt(e){try{let n=t(a(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),r=n.findIndex(e=>/^packages\\s*:/.test(e));if(r===-1)return null;let i=[];for(let e=r+1;e<n.length;e++){let t=n[e],r=/^\\s+-\\s+(.+)$/.exec(t);if(!r)break;i.push(r[1].trim().replace(/['\"`]/g,``))}return i.length>0?i:null}catch{return null}}const it=L({name:I()});function at(e){try{let n=t(a(e,`package.json`),`utf-8`),r=it.safeParse(JSON.parse(n));return r.success?r.data.name:null}catch{return null}}function Y(e){try{let n=t(a(e,`package.json`),`utf-8`),r=$e.safeParse(JSON.parse(n));return r.success?r.data.scripts:null}catch{return null}}const ot=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`]),st=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function X(e){return ot.has(e)?!0:st.some(t=>e.startsWith(t))}function ct(e,t){let n=z(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let a=r[1];return i.isAbsolute(a)?a:i.resolve(t.cwd,a)}function Z(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=je(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=ct(e.command,o);t&&(o={...o,cwd:t})}let t=lt(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 lt(e,t){let n=e.trim();if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=z(e);if(!r.ok)return{action:`passthrough`,rule:null,reason:r.error};let a=r.tokens;if(a.length===0)return{action:`passthrough`,rule:null,reason:null};let o=0;for(;o<a.length&&/^\\w+=/.test(a[o]);){let e=a[o].split(`=`)[0];if(X(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};o++}if(o>=a.length)return{action:`allow`,rule:`env-only`,reason:null};a=a.slice(o);let s=Me(a,e);if(!s.safe)return{action:`passthrough`,rule:null,reason:s.reason};for(let e=0;e<a.length;e++){let t=a[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(a[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=i.basename(a[0]);if(c===`command`&&a.length>1)return Z(a.slice(1).join(` `),{...t,depth:t.depth+1});if(c===`env`){let e=1;for(;e<a.length&&/^\\w+=/.test(a[e]);){let t=a[e].split(`=`)[0];if(X(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=a.length?{action:`allow`,rule:`env-only`,reason:null}:Z(a.slice(e).join(` `),{...t,depth:t.depth+1})}if(c===`pnpm`)return ut(a,t);if(c===`npm`)return pt(a);if(c===`bash`||c===`sh`||c===`zsh`)return gt(a,t);if(c===`node`&&(a.includes(`-e`)||a.includes(`-p`)||a.includes(`--eval`)||a.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(c===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let e of Qe)if(e.match.test(c)&&(!e.when||e.when(a,t)))return{action:`deny`,rule:e.name,reason:e.reason??null};for(let e of K)if(e.match.test(c)&&(!e.when||e.when(a,t)))return{action:`allow`,rule:e.name,reason:null};return{action:`passthrough`,rule:null,reason:`no rule matched: ${c}`}}function ut(e,t){let n=e.slice(1),r,i=-1,a=!1;for(let o=0;o<n.length;o++){let s=n[o];if(s===`--filter`||s.startsWith(`--filter=`))return mt(e,n,o,t);if(s===`-r`||s===`--recursive`){a=!0;continue}if(!(s.startsWith(`-`)&&!U.has(s))){r=s,i=o;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`}:Z(e.join(` `),{...t,depth:t.depth+1})}let o;if(r===`run`){let e=n[i+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};o=e}else if(!U.has(r))o=r;else{for(let n of K)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}}return a?ft(o,t):dt(o,t)}function dt(e,t){let n=q(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r={...t,depth:t.depth+1};if(n.pre){let e=Z(n.pre,r);if(e.action!==`allow`)return e}let i=Z(n.main,r);if(i.action!==`allow`)return i;if(n.post){let e=Z(n.post,r);if(e.action!==`allow`)return e}return{action:`allow`,rule:`pnpm-script:${e}`,reason:null}}function ft(e,t){let n=tt(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n={...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd};if(e.pre){let t=Z(e.pre,n);if(t.action!==`allow`)return t}let r=Z(e.main,n);if(r.action!==`allow`)return r;if(e.post){let t=Z(e.post,n);if(t.action!==`allow`)return t}}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function pt(e){for(let t of K)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:Ue(e)}}function mt(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(`-`)&&!U.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`}:Z(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(U.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=et(c,a,r.cwd);if(!l)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let u={...r,depth:r.depth+1,cwd:l.pkgDir??r.cwd};if(l.pre){let e=Z(l.pre,u);if(e.action!==`allow`)return e}let d=Z(l.main,u);if(d.action!==`allow`)return d;if(l.post){let e=Z(l.post,u);if(e.action!==`allow`)return e}return{action:`allow`,rule:`pnpm-filter:${a}:${c}`,reason:null}}const ht=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function gt(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||ht.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?Z(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 Q=await Ee();(Q.hook_event_name!==`PreToolUse`||Q.tool_name!==`Bash`)&&process.exit(0);const $=Z(Q.tool_input.command,{cwd:Q.cwd,depth:0});Ae({sessionId:Q.session_id,cwd:Q.cwd,command:Q.tool_input.command,decision:$.action,rule:$.rule,reason:$.reason}),$.action===`allow`?R(`allow`):$.action===`deny`&&R(`deny`,$.reason);export{};";
2410
+ const existing = ctx.read(HOOK_PATH);
2411
+ if (existing === content) return {
2412
+ filePath: HOOK_PATH,
2413
+ action: "skipped",
2414
+ description: "Hook is up to date"
2415
+ };
2416
+ ctx.write(HOOK_PATH, content);
2417
+ return {
2418
+ filePath: HOOK_PATH,
2419
+ action: existing ? "updated" : "created",
2420
+ description: existing ? "Updated bst-hook.mjs" : "Generated bst-hook.mjs"
2421
+ };
2422
+ }
2423
+ //#endregion
2353
2424
  //#region src/generators/claude-settings.ts
2425
+ const HookEntrySchema = z.object({
2426
+ type: z.string(),
2427
+ command: z.string()
2428
+ });
2429
+ const HookMatcherSchema = z.object({
2430
+ matcher: z.string(),
2431
+ hooks: z.array(HookEntrySchema).default([])
2432
+ });
2354
2433
  const ClaudeSettingsSchema = z.object({
2355
2434
  permissions: z.object({
2356
2435
  allow: z.array(z.string()).default([]),
@@ -2359,6 +2438,7 @@ const ClaudeSettingsSchema = z.object({
2359
2438
  allow: [],
2360
2439
  deny: []
2361
2440
  }),
2441
+ hooks: z.record(z.string(), z.array(HookMatcherSchema)).default({}),
2362
2442
  instructions: z.array(z.string()).default([]),
2363
2443
  enabledPlugins: z.record(z.string(), z.boolean()).default({}),
2364
2444
  extraKnownMarketplaces: z.record(z.string(), z.record(z.string(), z.unknown())).default({})
@@ -2378,66 +2458,7 @@ function parseClaudeSettings(raw) {
2378
2458
  }
2379
2459
  }
2380
2460
  function buildSettings(ctx) {
2381
- const pm = "pnpm";
2382
- const allow = [
2383
- `Bash(${pm} install)`,
2384
- `Bash(${pm} install *)`,
2385
- `Bash(${pm} add *)`,
2386
- `Bash(${pm} update *)`,
2387
- `Bash(${pm} view *)`,
2388
- `Bash(${pm} why *)`,
2389
- `Bash(${pm} list)`,
2390
- `Bash(${pm} list *)`,
2391
- `Bash(${pm} ls)`,
2392
- `Bash(${pm} ls *)`,
2393
- "Bash(npm view *)",
2394
- "Bash(npm info *)",
2395
- "Bash(npm show *)",
2396
- `Bash(${pm} build)`,
2397
- `Bash(${pm} -r build)`,
2398
- `Bash(${pm} dev)`,
2399
- `Bash(${pm} test)`,
2400
- `Bash(${pm} test *)`,
2401
- `Bash(${pm} -r test)`,
2402
- `Bash(${pm} vitest)`,
2403
- `Bash(${pm} vitest *)`,
2404
- `Bash(${pm} exec vitest)`,
2405
- `Bash(${pm} exec vitest *)`,
2406
- `Bash(${pm} exec oxfmt)`,
2407
- `Bash(${pm} exec oxfmt *)`,
2408
- `Bash(${pm} typecheck)`,
2409
- `Bash(${pm} -r --parallel run typecheck)`,
2410
- `Bash(${pm} tsc *)`,
2411
- `Bash(${pm} exec tsc *)`,
2412
- `Bash(${pm} lint)`,
2413
- `Bash(${pm} lint *)`,
2414
- `Bash(${pm} format)`,
2415
- `Bash(${pm} format *)`,
2416
- `Bash(${pm} knip)`,
2417
- `Bash(${pm} knip *)`,
2418
- `Bash(${pm} check)`,
2419
- `Bash(${pm} ci:check)`,
2420
- `Bash(${pm} exec oxlint *)`,
2421
- `Bash(${pm} exec knip *)`,
2422
- "Bash(git status *)",
2423
- "Bash(git log *)",
2424
- "Bash(git diff *)",
2425
- "Bash(git branch *)",
2426
- "Bash(git show *)",
2427
- "Bash(git rev-parse *)",
2428
- "Bash(ls *)",
2429
- "Bash(cat *)",
2430
- "Bash(head *)",
2431
- "Bash(tail *)",
2432
- "Bash(wc *)",
2433
- "Bash(test *)",
2434
- "Bash([ *)",
2435
- "Bash(grep *)",
2436
- "Bash(which *)",
2437
- "Bash(node -e *)",
2438
- "Bash(node -p *)"
2439
- ];
2440
- if (ctx.config.structure === "monorepo") allow.push(`Bash(${pm} --filter *)`, `Bash(${pm} -r *)`);
2461
+ const allow = [];
2441
2462
  const enabledPlugins = { "code-simplifier@claude-plugins-official": true };
2442
2463
  const extraKnownMarketplaces = {};
2443
2464
  if (ctx.config.structure !== "monorepo") {
@@ -2447,54 +2468,19 @@ function buildSettings(ctx) {
2447
2468
  permissions: {
2448
2469
  allow,
2449
2470
  deny: [
2450
- "Bash(npx *)",
2451
- "Bash(git push *)",
2452
- "Bash(git push)",
2453
- "Bash(git add *)",
2454
- "Bash(git add)",
2455
- "Bash(git commit *)",
2456
- "Bash(git commit)",
2457
- "Bash(git reset *)",
2458
- "Bash(git merge *)",
2459
- "Bash(git rebase *)",
2460
- "Bash(git cherry-pick *)",
2461
- "Bash(git checkout *)",
2462
- "Bash(git switch *)",
2463
- "Bash(git stash *)",
2464
- "Bash(git tag *)",
2465
- "Bash(git revert *)",
2466
- "Bash(git clean *)",
2467
- "Bash(git rm *)",
2468
- "Bash(git mv *)",
2469
- "Bash(rm -rf *)",
2470
- "Bash(cat *.env)",
2471
- "Bash(cat *.env.*)",
2472
- "Bash(cat .env)",
2473
- "Bash(cat .env.*)",
2474
- "Bash(head *.env)",
2475
- "Bash(head *.env.*)",
2476
- "Bash(head .env)",
2477
- "Bash(head .env.*)",
2478
- "Bash(tail *.env)",
2479
- "Bash(tail *.env.*)",
2480
- "Bash(tail .env)",
2481
- "Bash(tail .env.*)",
2482
- "Bash(less *.env)",
2483
- "Bash(less *.env.*)",
2484
- "Bash(less .env)",
2485
- "Bash(less .env.*)",
2486
- "Bash(more *.env)",
2487
- "Bash(more *.env.*)",
2488
- "Bash(more .env)",
2489
- "Bash(more .env.*)",
2490
- "Bash(grep * .env)",
2491
- "Bash(grep * .env.*)",
2492
2471
  "Read(.env)",
2493
2472
  "Read(.env.*)",
2494
2473
  "Read(*.env)",
2495
2474
  "Read(*.env.*)"
2496
2475
  ]
2497
2476
  },
2477
+ hooks: { PreToolUse: [{
2478
+ matcher: "Bash",
2479
+ hooks: [{
2480
+ type: "command",
2481
+ command: `node "$CLAUDE_PROJECT_DIR/.claude/hooks/bst-hook.mjs"`
2482
+ }]
2483
+ }] },
2498
2484
  instructions: [
2499
2485
  "Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`.",
2500
2486
  "No typecasts (as/any). Use zod schemas, type guards, or narrowing instead.",
@@ -2505,10 +2491,16 @@ function buildSettings(ctx) {
2505
2491
  extraKnownMarketplaces
2506
2492
  };
2507
2493
  }
2508
- /** Remove enabledPlugins/extraKnownMarketplaces when empty to keep the file clean. */
2494
+ const BST_HOOK_MARKER = "bst-hook.mjs";
2495
+ /** Check if a hook entry list already contains the bst-hook command. */
2496
+ function hasBstHook(matchers) {
2497
+ return matchers.some((m) => m.matcher === "Bash" && m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER)));
2498
+ }
2499
+ /** Remove enabledPlugins/extraKnownMarketplaces/hooks when empty to keep the file clean. */
2509
2500
  function serializeSettings$1(settings) {
2510
- const { enabledPlugins, extraKnownMarketplaces, ...rest } = settings;
2501
+ const { enabledPlugins, extraKnownMarketplaces, hooks, ...rest } = settings;
2511
2502
  const out = { ...rest };
2503
+ if (Object.keys(hooks).length > 0) out["hooks"] = hooks;
2512
2504
  if (Object.keys(enabledPlugins).length > 0) out["enabledPlugins"] = enabledPlugins;
2513
2505
  if (Object.keys(extraKnownMarketplaces).length > 0) out["extraKnownMarketplaces"] = extraKnownMarketplaces;
2514
2506
  return JSON.stringify(out, null, 2) + "\n";
@@ -2534,9 +2526,18 @@ function writeOrMergeSettings(ctx, filePath, generated) {
2534
2526
  else mergedInstructions.push(inst);
2535
2527
  instructionChanges++;
2536
2528
  }
2529
+ let hookChanges = 0;
2530
+ const mergedHooks = { ...settings.hooks };
2531
+ for (const [event, generatedMatchers] of Object.entries(generated.hooks)) {
2532
+ const existingMatchers = mergedHooks[event] ?? [];
2533
+ if (!hasBstHook(existingMatchers)) {
2534
+ mergedHooks[event] = [...existingMatchers, ...generatedMatchers];
2535
+ hookChanges++;
2536
+ }
2537
+ }
2537
2538
  const missingPlugins = Object.entries(generated.enabledPlugins).filter(([key]) => !(key in settings.enabledPlugins));
2538
2539
  const missingMarketplaces = Object.entries(generated.extraKnownMarketplaces).filter(([key]) => !(key in settings.extraKnownMarketplaces));
2539
- const changed = missingAllow.length + missingDeny.length + instructionChanges + missingPlugins.length + missingMarketplaces.length;
2540
+ const changed = missingAllow.length + missingDeny.length + hookChanges + instructionChanges + missingPlugins.length + missingMarketplaces.length;
2540
2541
  if (changed === 0) return {
2541
2542
  filePath,
2542
2543
  action: "skipped",
@@ -2546,6 +2547,7 @@ function writeOrMergeSettings(ctx, filePath, generated) {
2546
2547
  allow: [...settings.permissions.allow, ...missingAllow],
2547
2548
  deny: [...settings.permissions.deny, ...missingDeny]
2548
2549
  };
2550
+ if (Object.keys(mergedHooks).length > 0) rawJson["hooks"] = mergedHooks;
2549
2551
  rawJson["instructions"] = mergedInstructions;
2550
2552
  const updatedPlugins = { ...settings.enabledPlugins };
2551
2553
  for (const [key, value] of missingPlugins) updatedPlugins[key] = value;
@@ -2581,6 +2583,7 @@ async function generateClaudeSettings(ctx) {
2581
2583
  allow: [],
2582
2584
  deny: []
2583
2585
  },
2586
+ hooks: {},
2584
2587
  instructions: [],
2585
2588
  enabledPlugins: { "frontend-design@claude-plugins-official": true },
2586
2589
  extraKnownMarketplaces: {}
@@ -3181,6 +3184,7 @@ async function runGenerators(ctx) {
3181
3184
  results.push(await generateKnip(ctx));
3182
3185
  results.push(await generateRenovate(ctx));
3183
3186
  results.push(await generateCi(ctx));
3187
+ results.push(await generateClaudeHooks(ctx));
3184
3188
  results.push(...await generateClaudeSettings(ctx));
3185
3189
  results.push(await generateReleaseIt(ctx));
3186
3190
  results.push(await generateVersionrc(ctx));
@@ -3201,7 +3205,7 @@ async function runGenerators(ctx) {
3201
3205
  function generateMigratePrompt(results, config, detected) {
3202
3206
  const sections = [];
3203
3207
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3204
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.38.0 repo:sync\` on ${timestamp}_\n`);
3208
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.40.0 repo:sync\` on ${timestamp}_\n`);
3205
3209
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
3206
3210
  if (changed.length > 0) {
3207
3211
  const items = changed.map((r) => {
@@ -3501,7 +3505,8 @@ async function runCheck(targetDir) {
3501
3505
  if (!newContent) continue;
3502
3506
  const existingPath = path.join(targetDir, r.filePath);
3503
3507
  const existing = existsSync(existingPath) ? readFileSync(existingPath, "utf-8") : void 0;
3504
- if (!existing) {
3508
+ if (isBundledFile(r.filePath)) log.info(` bundled file changed (${formatBytes(existing?.length ?? 0)} → ${formatBytes(newContent.length)})`);
3509
+ else if (!existing) {
3505
3510
  const lineCount = newContent.split("\n").length - 1;
3506
3511
  log.info(` + ${lineCount} new lines`);
3507
3512
  } else {
@@ -3511,6 +3516,14 @@ async function runCheck(targetDir) {
3511
3516
  }
3512
3517
  return 1;
3513
3518
  }
3519
+ /** Files that are minified/bundled and produce unreadable diffs. */
3520
+ function isBundledFile(filePath) {
3521
+ return filePath.endsWith("bst-hook.mjs");
3522
+ }
3523
+ function formatBytes(bytes) {
3524
+ if (bytes < 1024) return `${String(bytes)} B`;
3525
+ return `${(bytes / 1024).toFixed(1)} kB`;
3526
+ }
3514
3527
  const normalize = (line) => line.trimEnd();
3515
3528
  function lineDiff(oldText, newText) {
3516
3529
  const oldLines = oldText.split("\n").map(normalize);
@@ -3818,6 +3831,30 @@ async function ensureRelease(executor, conn, tag) {
3818
3831
  //#endregion
3819
3832
  //#region src/release/version.ts
3820
3833
  const BRANCH = "changeset-release/main";
3834
+ /** Pattern matching a dateless version heading like `## 1.2.3` or `## 1.2.3-beta.0`. */
3835
+ const DATELESS_VERSION_RE = /^## (\d+\.\d+\.\d+(?:-[^\s)]+)?)$/gm;
3836
+ /** Format today's date as YYYY-MM-DD. */
3837
+ function todayIso() {
3838
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3839
+ }
3840
+ /** Add today's date to dateless version headings in a changelog string. */
3841
+ function stampDates(content, date) {
3842
+ return content.replace(DATELESS_VERSION_RE, `## $1 (${date})`);
3843
+ }
3844
+ /**
3845
+ * Stamp release dates on CHANGELOGs for packages that were just versioned.
3846
+ * Only modifies headings that don't already have a date (idempotent).
3847
+ */
3848
+ function stampChangelogDates(executor, cwd, changedDirs, isMonorepo) {
3849
+ const date = todayIso();
3850
+ const changelogPaths = isMonorepo ? changedDirs.map((dir) => path.join(cwd, "packages", dir, "CHANGELOG.md")) : [path.join(cwd, "CHANGELOG.md")];
3851
+ for (const changelogPath of changelogPaths) {
3852
+ const content = executor.readFile(changelogPath);
3853
+ if (!content) continue;
3854
+ const updated = stampDates(content, date);
3855
+ if (updated !== content) executor.writeFile(changelogPath, updated);
3856
+ }
3857
+ }
3821
3858
  /** Extract the latest changelog entry (content between first and second `## ` heading). */
3822
3859
  function extractLatestEntry(changelog) {
3823
3860
  const lines = changelog.split("\n");
@@ -3905,6 +3942,12 @@ async function runVersionMode(executor, config) {
3905
3942
  debugExec(config, "pnpm changeset version", versionResult);
3906
3943
  if (originalConfig) executor.writeFile(changesetConfigPath, originalConfig);
3907
3944
  if (versionResult.exitCode !== 0) throw new FatalError(`pnpm changeset version failed (exit code ${String(versionResult.exitCode)}):\n${versionResult.stderr}`);
3945
+ const packagesAfterVersion = executor.listWorkspacePackages(config.cwd);
3946
+ if (packagesBefore.length > 0 || packagesAfterVersion.length > 0) {
3947
+ const beforeMap = new Map(packagesBefore.map((pkg) => [pkg.name, pkg.version]));
3948
+ const changedDirs = packagesAfterVersion.filter((pkg) => beforeMap.get(pkg.name) !== pkg.version).map((pkg) => pkg.dir);
3949
+ stampChangelogDates(executor, config.cwd, changedDirs, true);
3950
+ } else stampChangelogDates(executor, config.cwd, [], false);
3908
3951
  debugExec(config, "pnpm install --no-frozen-lockfile", executor.exec("pnpm", ["install", "--no-frozen-lockfile"], { cwd: config.cwd }));
3909
3952
  const { title, body } = buildPrContent(executor, config.cwd, packagesBefore);
3910
3953
  debug(config, `PR title: ${title}`);
@@ -5173,11 +5216,122 @@ const dockerCheckCommand = defineCommand({
5173
5216
  }
5174
5217
  });
5175
5218
  //#endregion
5219
+ //#region src/commands/hooks-audit.ts
5220
+ const AuditEntrySchema = z.object({
5221
+ timestamp: z.string(),
5222
+ sessionId: z.string(),
5223
+ cwd: z.string().optional(),
5224
+ command: z.string(),
5225
+ decision: z.enum([
5226
+ "allow",
5227
+ "deny",
5228
+ "passthrough"
5229
+ ]),
5230
+ rule: z.string().nullable(),
5231
+ reason: z.string().nullable()
5232
+ });
5233
+ const write = (msg = "") => console.log(msg);
5234
+ function parseAuditLog(logPath) {
5235
+ if (!existsSync(logPath)) return [];
5236
+ const lines = readFileSync(logPath, "utf-8").split("\n").filter(Boolean);
5237
+ const entries = [];
5238
+ for (const line of lines) try {
5239
+ const result = AuditEntrySchema.safeParse(JSON.parse(line));
5240
+ if (result.success) entries.push(result.data);
5241
+ } catch (_error) {}
5242
+ return entries;
5243
+ }
5244
+ /** Count occurrences of each unique command, sorted descending. */
5245
+ function topCommands(entries, limit) {
5246
+ const counts = /* @__PURE__ */ new Map();
5247
+ for (const e of entries) {
5248
+ const existing = counts.get(e.command);
5249
+ if (existing) existing.count++;
5250
+ else counts.set(e.command, {
5251
+ command: e.command,
5252
+ count: 1,
5253
+ rule: e.rule
5254
+ });
5255
+ }
5256
+ return [...counts.values()].toSorted((a, b) => b.count - a.count).slice(0, limit);
5257
+ }
5258
+ function printSection(title, entries, limit) {
5259
+ const top = topCommands(entries, limit);
5260
+ if (top.length === 0) return;
5261
+ write(title);
5262
+ for (const { command, count, rule } of top) {
5263
+ const ruleSuffix = rule ? ` [${rule}]` : "";
5264
+ write(` ${String(count).padStart(4)}x ${command}${ruleSuffix}`);
5265
+ }
5266
+ write();
5267
+ }
5268
+ const hooksAuditCommand = defineCommand({
5269
+ meta: {
5270
+ name: "hooks:audit",
5271
+ description: "Summarize the bst-hook audit log"
5272
+ },
5273
+ args: {
5274
+ dir: {
5275
+ type: "positional",
5276
+ description: "Project directory (default: current directory)",
5277
+ required: false
5278
+ },
5279
+ tail: {
5280
+ type: "string",
5281
+ description: "Show the last N raw entries"
5282
+ },
5283
+ decision: {
5284
+ type: "string",
5285
+ description: "Filter by decision: allow, deny, or passthrough"
5286
+ },
5287
+ json: {
5288
+ type: "boolean",
5289
+ description: "Output raw JSON lines"
5290
+ }
5291
+ },
5292
+ async run({ args }) {
5293
+ const targetDir = path.resolve(args.dir ?? ".");
5294
+ let entries = parseAuditLog(path.join(targetDir, ".claude/hooks/bst-hook.audit.local.jsonl"));
5295
+ if (entries.length === 0) {
5296
+ write("No audit log entries found.");
5297
+ return;
5298
+ }
5299
+ const decisionFilter = args.decision;
5300
+ if (decisionFilter) entries = entries.filter((e) => e.decision === decisionFilter);
5301
+ if (args.json === true) {
5302
+ const tail = args.tail ? Number.parseInt(args.tail, 10) : entries.length;
5303
+ for (const entry of entries.slice(-tail)) write(JSON.stringify(entry));
5304
+ return;
5305
+ }
5306
+ if (args.tail) {
5307
+ const tailCount = Number.parseInt(args.tail, 10);
5308
+ for (const entry of entries.slice(-tailCount)) {
5309
+ const ts = new Date(entry.timestamp).toLocaleString();
5310
+ const rule = entry.rule ? ` [${entry.rule}]` : "";
5311
+ write(`${ts} ${entry.decision.padEnd(12)}${entry.command}${rule}`);
5312
+ }
5313
+ return;
5314
+ }
5315
+ const counts = {
5316
+ allow: 0,
5317
+ deny: 0,
5318
+ passthrough: 0
5319
+ };
5320
+ for (const e of entries) counts[e.decision]++;
5321
+ write(`${String(entries.length)} entries: ${String(counts.allow)} allow, ${String(counts.deny)} deny, ${String(counts.passthrough)} passthrough`);
5322
+ write();
5323
+ const denied = entries.filter((e) => e.decision === "deny");
5324
+ const passthrough = entries.filter((e) => e.decision === "passthrough");
5325
+ printSection("Denied commands:", denied, 10);
5326
+ printSection("Passthrough commands:", passthrough, 10);
5327
+ }
5328
+ });
5329
+ //#endregion
5176
5330
  //#region src/bin.ts
5177
5331
  const main = defineCommand({
5178
5332
  meta: {
5179
5333
  name: "bst",
5180
- version: "0.38.0",
5334
+ version: "0.40.0",
5181
5335
  description: "Bootstrap and maintain standardized TypeScript project tooling"
5182
5336
  },
5183
5337
  subCommands: {
@@ -5190,10 +5344,11 @@ const main = defineCommand({
5190
5344
  "release:simple": releaseSimpleCommand,
5191
5345
  "docker:publish": publishDockerCommand,
5192
5346
  "docker:build": dockerBuildCommand,
5193
- "docker:check": dockerCheckCommand
5347
+ "docker:check": dockerCheckCommand,
5348
+ "hooks:audit": hooksAuditCommand
5194
5349
  }
5195
5350
  });
5196
- console.log(`@bensandee/tooling v0.38.0`);
5351
+ console.log(`@bensandee/tooling v0.40.0`);
5197
5352
  async function run() {
5198
5353
  await runMain(main);
5199
5354
  process.exit(process.exitCode ?? 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.38.0",
3
+ "version": "0.40.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -60,7 +60,8 @@
60
60
  "tsdown": "0.21.4",
61
61
  "typescript": "5.9.3",
62
62
  "vitest": "4.1.0",
63
- "@bensandee/config": "0.9.2"
63
+ "@bensandee/config": "0.10.0",
64
+ "@bensandee/claude-hooks": "0.2.0"
64
65
  },
65
66
  "scripts": {
66
67
  "build": "tsdown",