@bensandee/tooling 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +129 -20
- package/package.json +3 -3
- package/tooling.schema.json +7 -0
package/dist/bin.mjs
CHANGED
|
@@ -626,7 +626,8 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
626
626
|
dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional().meta({ description: "Docker health check configuration or false to disable" }),
|
|
627
627
|
registryHost: z.string().optional().meta({ description: "Docker registry hostname (e.g. ghcr.io, code.example.com)" }),
|
|
628
628
|
registryNamespace: z.string().optional().meta({ description: "Docker registry namespace (e.g. ghcr.io/myorg, code.example.com/myorg)" }),
|
|
629
|
-
postReleaseHook: z.string().optional().meta({ description: "Override the post-release hook command (default: curl POST_RELEASE_HOOK_URL secret if set)" })
|
|
629
|
+
postReleaseHook: z.string().optional().meta({ description: "Override the post-release hook command (default: curl POST_RELEASE_HOOK_URL secret if set)" }),
|
|
630
|
+
postReleaseHookSecrets: z.array(z.string()).optional().meta({ description: "Additional secret names to inject as env vars in the post-release hook step (e.g. ['WEBHOOK_SECRET'])" })
|
|
630
631
|
});
|
|
631
632
|
/** Load saved tooling config from the target directory. Returns undefined if missing, throws on invalid. */
|
|
632
633
|
function loadToolingConfig(targetDir) {
|
|
@@ -1206,15 +1207,26 @@ function publishSteps(nodeVersionYaml, registryDerivable) {
|
|
|
1206
1207
|
];
|
|
1207
1208
|
}
|
|
1208
1209
|
const DockerMapSchema = z.object({ docker: z.record(z.string(), z.unknown()).optional() });
|
|
1209
|
-
const PostReleaseHookSchema = z.object({
|
|
1210
|
+
const PostReleaseHookSchema$1 = z.object({
|
|
1211
|
+
postReleaseHook: z.string().optional(),
|
|
1212
|
+
postReleaseHookSecrets: z.array(z.string()).optional()
|
|
1213
|
+
});
|
|
1210
1214
|
/** Read the postReleaseHook command override from .tooling.json, if configured. */
|
|
1211
1215
|
function getPostReleaseHookCommand(ctx) {
|
|
1212
1216
|
const raw = ctx.read(".tooling.json");
|
|
1213
1217
|
if (!raw) return void 0;
|
|
1214
|
-
const result = PostReleaseHookSchema.safeParse(JSON.parse(raw));
|
|
1218
|
+
const result = PostReleaseHookSchema$1.safeParse(JSON.parse(raw));
|
|
1215
1219
|
if (!result.success) return void 0;
|
|
1216
1220
|
return result.data.postReleaseHook;
|
|
1217
1221
|
}
|
|
1222
|
+
/** Read the postReleaseHookSecrets array from .tooling.json, if configured. */
|
|
1223
|
+
function getPostReleaseHookSecrets(ctx) {
|
|
1224
|
+
const raw = ctx.read(".tooling.json");
|
|
1225
|
+
if (!raw) return void 0;
|
|
1226
|
+
const result = PostReleaseHookSchema$1.safeParse(JSON.parse(raw));
|
|
1227
|
+
if (!result.success) return void 0;
|
|
1228
|
+
return result.data.postReleaseHookSecrets;
|
|
1229
|
+
}
|
|
1218
1230
|
/** Get names of packages that have Docker builds (by convention or .tooling.json config). */
|
|
1219
1231
|
function getDockerPackageNames(ctx) {
|
|
1220
1232
|
const names = [];
|
|
@@ -1559,7 +1571,7 @@ async function generatePackageJson(ctx) {
|
|
|
1559
1571
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1560
1572
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1561
1573
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.1";
|
|
1562
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1574
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.43.0";
|
|
1563
1575
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1564
1576
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1565
1577
|
"@changesets/cli": "2.30.0",
|
|
@@ -2167,13 +2179,29 @@ async function generateGitignore(ctx) {
|
|
|
2167
2179
|
}
|
|
2168
2180
|
//#endregion
|
|
2169
2181
|
//#region src/generators/release-ci.ts
|
|
2170
|
-
const DEFAULT_POST_RELEASE_COMMAND =
|
|
2182
|
+
const DEFAULT_POST_RELEASE_COMMAND = [
|
|
2183
|
+
"if [ -n \"$POST_RELEASE_HOOK_URL\" ]; then",
|
|
2184
|
+
" curl -fsS -X POST",
|
|
2185
|
+
" $",
|
|
2186
|
+
"{POST_RELEASE_HOOK_SECRET:+-H \"Authorization: Bearer $POST_RELEASE_HOOK_SECRET\"}",
|
|
2187
|
+
" \"$POST_RELEASE_HOOK_URL\"; fi"
|
|
2188
|
+
].join("");
|
|
2189
|
+
/** Build env vars for hook secrets to inject into a Release step (for runtime hook execution). */
|
|
2190
|
+
function hookSecretEnv(secrets) {
|
|
2191
|
+
const env = {
|
|
2192
|
+
POST_RELEASE_HOOK_URL: actionsExpr("secrets.POST_RELEASE_HOOK_URL"),
|
|
2193
|
+
POST_RELEASE_HOOK_SECRET: actionsExpr("secrets.POST_RELEASE_HOOK_SECRET")
|
|
2194
|
+
};
|
|
2195
|
+
for (const name of secrets ?? []) env[name] = actionsExpr(`secrets.${name}`);
|
|
2196
|
+
return env;
|
|
2197
|
+
}
|
|
2171
2198
|
/** Step that runs after release, gated on the POST_RELEASE_HOOK_URL secret being set. */
|
|
2172
|
-
function postReleaseHookStep(
|
|
2199
|
+
function postReleaseHookStep(opts) {
|
|
2173
2200
|
return { step: {
|
|
2174
2201
|
name: "Post-release hook",
|
|
2175
|
-
|
|
2176
|
-
|
|
2202
|
+
...opts?.guardMain && { if: "github.ref == 'refs/heads/main'" },
|
|
2203
|
+
env: hookSecretEnv(opts?.secrets),
|
|
2204
|
+
run: opts?.command ?? DEFAULT_POST_RELEASE_COMMAND
|
|
2177
2205
|
} };
|
|
2178
2206
|
}
|
|
2179
2207
|
/** Common setup steps shared by release-it and simple strategies. */
|
|
@@ -2255,7 +2283,7 @@ function dockerPublishEnv(registryDerivable) {
|
|
|
2255
2283
|
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2256
2284
|
};
|
|
2257
2285
|
}
|
|
2258
|
-
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2286
|
+
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable, hookSecrets) {
|
|
2259
2287
|
const releaseStep = { step: {
|
|
2260
2288
|
name: "Release",
|
|
2261
2289
|
env: {
|
|
@@ -2264,6 +2292,7 @@ function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, regist
|
|
|
2264
2292
|
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2265
2293
|
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
2266
2294
|
},
|
|
2295
|
+
...hookSecretEnv(hookSecrets),
|
|
2267
2296
|
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2268
2297
|
},
|
|
2269
2298
|
run: simpleReleaseCommand()
|
|
@@ -2280,10 +2309,10 @@ function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, regist
|
|
|
2280
2309
|
...hasDocker ? [dockerStep] : []
|
|
2281
2310
|
];
|
|
2282
2311
|
}
|
|
2283
|
-
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2312
|
+
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable, hookSecrets) {
|
|
2284
2313
|
switch (strategy) {
|
|
2285
2314
|
case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
|
|
2286
|
-
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable);
|
|
2315
|
+
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable, hookSecrets);
|
|
2287
2316
|
default: return null;
|
|
2288
2317
|
}
|
|
2289
2318
|
}
|
|
@@ -2305,13 +2334,17 @@ async function generateReleaseCi(ctx) {
|
|
|
2305
2334
|
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
2306
2335
|
const hasDocker = hasDockerPackages(ctx);
|
|
2307
2336
|
const registryDerivable = hasRepositoryUrl(ctx);
|
|
2308
|
-
const
|
|
2337
|
+
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
2338
|
+
const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable, hookSecrets);
|
|
2309
2339
|
if (!steps) return {
|
|
2310
2340
|
filePath,
|
|
2311
2341
|
action: "skipped",
|
|
2312
2342
|
description: "Release CI workflow not applicable"
|
|
2313
2343
|
};
|
|
2314
|
-
steps.push(postReleaseHookStep(
|
|
2344
|
+
if (ctx.config.releaseStrategy === "release-it") steps.push(postReleaseHookStep({
|
|
2345
|
+
command: getPostReleaseHookCommand(ctx),
|
|
2346
|
+
secrets: hookSecrets
|
|
2347
|
+
}));
|
|
2315
2348
|
const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
|
|
2316
2349
|
const content = buildWorkflowYaml({
|
|
2317
2350
|
ci: ctx.config.ci,
|
|
@@ -2347,7 +2380,7 @@ async function generateReleaseCi(ctx) {
|
|
|
2347
2380
|
//#endregion
|
|
2348
2381
|
//#region src/generators/ci.ts
|
|
2349
2382
|
/** Build the release step for the check job (changesets strategy). */
|
|
2350
|
-
function changesetsReleaseStep(ci, publishesNpm) {
|
|
2383
|
+
function changesetsReleaseStep(ci, publishesNpm, hookSecrets) {
|
|
2351
2384
|
if (ci === "github") return { step: {
|
|
2352
2385
|
uses: "changesets/action@v1",
|
|
2353
2386
|
if: "github.ref == 'refs/heads/main'",
|
|
@@ -2368,6 +2401,7 @@ function changesetsReleaseStep(ci, publishesNpm) {
|
|
|
2368
2401
|
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2369
2402
|
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
2370
2403
|
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") },
|
|
2404
|
+
...hookSecretEnv(hookSecrets),
|
|
2371
2405
|
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2372
2406
|
},
|
|
2373
2407
|
run: "pnpm exec bst release:changesets"
|
|
@@ -2416,10 +2450,16 @@ async function generateCi(ctx) {
|
|
|
2416
2450
|
const nodeVersionYaml = computeNodeVersionYaml(ctx);
|
|
2417
2451
|
const publishesNpm = ctx.config.publishNpm === true;
|
|
2418
2452
|
const hookCommand = getPostReleaseHookCommand(ctx);
|
|
2453
|
+
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
2454
|
+
const needsHookStep = isChangesets && isGitHub;
|
|
2419
2455
|
const steps = [
|
|
2420
2456
|
...checkSteps(nodeVersionYaml, isChangesets && publishesNpm),
|
|
2421
|
-
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm)] : [],
|
|
2422
|
-
...
|
|
2457
|
+
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm, hookSecrets)] : [],
|
|
2458
|
+
...needsHookStep ? [postReleaseHookStep({
|
|
2459
|
+
command: hookCommand,
|
|
2460
|
+
secrets: hookSecrets,
|
|
2461
|
+
guardMain: true
|
|
2462
|
+
})] : []
|
|
2423
2463
|
];
|
|
2424
2464
|
const content = buildWorkflowYaml({
|
|
2425
2465
|
ci: ctx.config.ci,
|
|
@@ -2667,7 +2707,7 @@ function writeIfChanged(ctx, filePath, content, label) {
|
|
|
2667
2707
|
};
|
|
2668
2708
|
}
|
|
2669
2709
|
async function generateClaudeHooks(ctx) {
|
|
2670
|
-
return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.0\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport{a as e,i as t,n,o as r,r as i,t as a}from\"./file-rules-RB-fEyic.mjs\";import*as o from\"node:fs\";import{existsSync as s,readFileSync as c,readdirSync as l,statSync as u}from\"node:fs\";import*as d from\"node:path\";import{dirname as ee,join as f,resolve as p}from\"node:path\";import{fileURLToPath as te}from\"node:url\";Object.freeze({status:`aborted`});function m(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 h=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const g={};function _(e){return e&&Object.assign(g,e),g}function ne(e,t){return typeof t==`bigint`?t.toString():t}function re(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ie(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const v=Symbol(`evaluating`);function y(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==v)return r===void 0&&(r=v,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const b=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function x(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ae(e){if(x(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(x(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function oe(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function S(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function se(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function C(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 w(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function T(e){return typeof e==`string`?e:e?.message}function E(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=T(e.inst?._zod.def?.error?.(e))??T(t?.error?.(e))??T(n.customError?.(e))??T(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const ce=(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,ne,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},le=m(`$ZodError`,ce),D=m(`$ZodError`,ce,{Parent:Error}),ue=(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 h;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>E(e,a,_())));throw b(t,i?.callee),t}return o.value})(D),de=(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=>E(e,a,_())));throw b(t,i?.callee),t}return o.value})(D),O=(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 h;return a.issues.length?{success:!1,error:new(e??le)(a.issues.map(e=>E(e,i,_())))}:{success:!0,data:a.value}})(D),k=(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=>E(e,i,_())))}:{success:!0,data:a.value}})(D),fe=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},pe=/^-?\\d+(?:\\.\\d+)?$/,me={major:4,minor:3,patch:6},A=m(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=me;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=C(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 h;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=C(e,t))});else{if(e.issues.length===t)continue;r||=C(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(C(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new h;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 h;return o.then(e=>t(e,r,a))}return t(o,r,a)}}y(e,`~standard`,()=>({validate:t=>{try{let n=O(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return k(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),he=m(`$ZodString`,(e,t)=>{A.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??fe(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}}),ge=m(`$ZodUnknown`,(e,t)=>{A.init(e,t),e._zod.parse=e=>e});function j(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...w(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function _e(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=se(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function ve(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=>j(e,n,i,t,u))):j(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 ye=m(`$ZodObject`,(e,t)=>{if(A.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=re(()=>_e(t));y(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=x,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=>j(n,t,e,s,r))):j(i,t,e,s,r)}return i?ve(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),be=m(`$ZodRecord`,(e,t)=>{A.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!ae(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...w(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...w(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`&&pe.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=>E(e,r,_())),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(...w(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...w(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function M(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const xe=m(`$ZodOptional`,(e,t)=>{A.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,y(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),y(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ie(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=>M(t,e.value)):M(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var N,Se=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 Ce(){return new Se}(N=globalThis).__zod_globalRegistry??(N.__zod_globalRegistry=Ce()),globalThis.__zod_globalRegistry;function we(e,t){return new e({type:`string`,...S(t)})}function Te(e){return new e({type:`unknown`})}const P=m(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);A.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>ue(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>O(e,t,n),e.parseAsync=async(t,n)=>de(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>k(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>oe(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),Ee=m(`ZodMiniString`,(e,t)=>{he.init(e,t),P.init(e,t)});function F(e){return we(Ee,e)}const De=m(`ZodMiniUnknown`,(e,t)=>{ge.init(e,t),P.init(e,t)});function I(){return Te(De)}const Oe=m(`ZodMiniObject`,(e,t)=>{ye.init(e,t),P.init(e,t),y(e,`shape`,()=>t.shape)});function L(e,t){return new Oe({type:`object`,shape:e??{},...S(t)})}const ke=m(`ZodMiniRecord`,(e,t)=>{be.init(e,t),P.init(e,t)});function R(e,t,n){return new ke({type:`record`,keyType:e,valueType:t,...S(n)})}const Ae=m(`ZodMiniOptional`,(e,t)=>{xe.init(e,t),P.init(e,t)});function z(e){return new Ae({type:`optional`,innerType:e})}const je=L({session_id:F(),transcript_path:F(),cwd:F(),permission_mode:F(),hook_event_name:F(),tool_name:F(),tool_input:R(F(),I())});async function Me(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=je.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Ne=L({command:F(),description:z(F())}),Pe=L({file_path:F()}),Fe=L({pattern:F(),path:z(F())});function Ie(e){let t=Ne.safeParse(e);return t.success?t.data:null}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 B(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function ze(){let e=d.dirname(te(import.meta.url));return d.join(e,`bst-hook.audit.local.jsonl`)}function Be(e){try{if(o.statSync(e).size>1048576){let t=`${e}.1`;o.renameSync(e,t)}}catch{}}let Ve=!1;function He(e){try{let t=ze();Ve||=(Be(t),!0);let n={timestamp:new Date().toISOString(),...e};o.appendFileSync(t,JSON.stringify(n)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function V(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 Ue(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]===`&`){H(t,n,`&&`),n=``,r+=2;continue}if(o===`&`&&!(r+1<e.length&&e[r+1]===`>`)&&!(r>0&&(e[r-1]===`>`||e[r-1]===`<`))){H(t,n,`&`),n=``,r++;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){H(t,n,`||`),n=``,r+=2;continue}if(o===`|`){H(t,n,`|`),n=``,r++;continue}if(o===`;`){H(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function H(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const U=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function We(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(`>(`)&&!U.has(t))return{safe:!1,reason:`process substitution`}}for(let t=0;t<e.length;t++){let n=e[t];if(!U.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 Ge=L({scripts:R(F(),I())});function W(e,t){let n=$e(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 Ke(e,t,n){let r=Ye(t,n);if(!r)return null;let i=W(e,r);return i?{...i,pkgDir:r}:null}function qe(e,t){let n=G(t);if(!n)return null;let r=[];for(let t of n){let n=W(e,t);n&&r.push({...n,pkgDir:t})}return r}function Je(e){let t=p(e);for(;;){if(s(f(t,`pnpm-workspace.yaml`)))return t;let e=ee(t);if(e===t)return null;t=e}}function G(e){let t=Xe(e);if(!t)return null;let n=[];for(let r of t){if(!r.endsWith(`/*`))continue;let t=p(e,r.slice(0,-2)),i;try{i=l(t)}catch{continue}for(let e of i){let r=f(t,e);try{if(!u(r).isDirectory())continue}catch{continue}n.push(r)}}return n.length>0?n:null}function Ye(e,t){let n=G(t);if(!n)return null;for(let t of n){let n=Qe(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function Xe(e){try{let t=c(f(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),n=t.findIndex(e=>/^packages\\s*:/.test(e));if(n===-1)return null;let r=[];for(let e=n+1;e<t.length;e++){let n=t[e],i=/^\\s+-\\s+(.+)$/.exec(n);if(!i)break;r.push(i[1].trim().replace(/['\"`]/g,``))}return r.length>0?r:null}catch{return null}}const Ze=L({name:F()});function Qe(e){try{let t=c(f(e,`package.json`),`utf-8`),n=Ze.safeParse(JSON.parse(t));return n.success?n.data.name:null}catch{return null}}function $e(e){try{let t=c(f(e,`package.json`),`utf-8`),n=Ge.safeParse(JSON.parse(t));return n.success?n.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`]),tt=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function K(e){return et.has(e)?!0:tt.some(t=>e.startsWith(t))}function nt(e,t){let n=V(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 d.isAbsolute(i)?i:d.resolve(t.cwd,i)}function q(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=Ue(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=nt(e.command,o);t&&(o={...o,cwd:t})}let t=rt(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 J(e,t){if(e.pre){let n=q(e.pre,t);if(n.action!==`allow`)return n}let n=q(e.main,t);if(n.action!==`allow`)return n;if(e.post){let n=q(e.post,t);if(n.action!==`allow`)return n}return`allow`}function rt(n,r){let i=n.trim();if(i.startsWith(`(`)&&i.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let a=V(n);if(!a.ok)return{action:`passthrough`,rule:null,reason:a.error};let o=a.tokens;if(o.length===0)return{action:`passthrough`,rule:null,reason:null};let s=0;for(;s<o.length&&/^\\w+=/.test(o[s]);){let e=o[s].split(`=`)[0];if(K(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};s++}if(s>=o.length)return{action:`allow`,rule:`env-only`,reason:null};o=o.slice(s);let c=We(o,n);if(!c.safe)return{action:`passthrough`,rule:null,reason:c.reason};for(let e=0;e<o.length;e++){let t=o[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(o[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 l=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);o=o.filter((e,t)=>!(l.has(e)||(e===`>`||e===`>>`)&&o[t+1]===`/dev/null`||e===`/dev/null`&&t>0&&(o[t-1]===`>`||o[t-1]===`>>`)));let u=d.basename(o[0]);if(u===`command`&&o.length>1)return q(o.slice(1).join(` `),{...r,depth:r.depth+1});if(u===`env`){let e=1;for(;e<o.length&&/^\\w+=/.test(o[e]);){let t=o[e].split(`=`)[0];if(K(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=o.length?{action:`allow`,rule:`env-only`,reason:null}:q(o.slice(e).join(` `),{...r,depth:r.depth+1})}if(u===`pnpm`)return it(o,r);if(u===`npm`)return ot(o);if(u===`bash`||u===`sh`||u===`zsh`)return lt(o,r);if(u===`xargs`)return dt(o,r);if(u===`node`&&(o.includes(`-e`)||o.includes(`-p`)||o.includes(`--eval`)||o.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(u===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let t of e)if(t.match.test(u)&&(!t.when||t.when(o,r)))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let e of t)if(e.match.test(u)&&(!e.when||e.when(o,r)))return{action:`allow`,rule:e.name,reason:null};return{action:`passthrough`,rule:null,reason:`no rule matched: ${u}`}}function it(e,n){let r=e.slice(1),a,o=-1,s=!1,c=!1,l;for(let t=0;t<r.length;t++){let u=r[t];if(u===`--filter`||u.startsWith(`--filter=`))return st(e,r,t,n);if(u===`-r`||u===`--recursive`){s=!0;continue}if(u===`-w`||u===`--workspace-root`){c=!0;continue}if(u===`-C`||u===`--dir`){l=r[++t];continue}if(u.startsWith(`-C=`)||u.startsWith(`--dir=`)){l=u.split(`=`,2)[1];continue}if(!(u.startsWith(`-`)&&!i.has(u))){a=u,o=t;break}}if(!a)return{action:`passthrough`,rule:null,reason:null};if(a===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(a===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(a===`exec`){let e=r.slice(o+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:q(e.join(` `),{...n,depth:n.depth+1})}let u;if(a===`run`){let e=r[o+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};u=e}else if(!i.has(a))u=a;else{for(let r of t)if(r.match.test(`pnpm`)&&(!r.when||r.when(e,n)))return{action:`allow`,rule:r.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}if(s)return at(u,n);if(c){let e=Je(n.cwd);return e?Y(u,{...n,cwd:e}):{action:`passthrough`,rule:null,reason:`workspace root not found`}}if(l){let e=d.resolve(n.cwd,l);return Y(u,{...n,cwd:e})}return Y(u,n)}function Y(e,t){let n=W(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r=J(n,{...t,depth:t.depth+1});return r===`allow`?{action:`allow`,rule:`pnpm-script:${e}`,reason:null}:r}function at(e,t){let n=qe(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n=J(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 ot(e){for(let n of t)if(n.match.test(`npm`)&&(!n.when||n.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:n.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:r(e)}}function st(e,t,n,r){let a=t[n],o,s;if(a.startsWith(`--filter=`))o=a.slice(9),s=n+1;else{if(o=t[n+1],!o||o.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};s=n+2}if(!o)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let c;for(let e=s;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!i.has(n))){c=n;break}}if(!c)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(c===`install`||c===`i`)return t.slice(t.indexOf(c)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(c===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(c===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(c))return{action:`allow`,rule:`pnpm`,reason:null};if(c===`exec`){let e=t.indexOf(`exec`,s),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:q(n.join(` `),{...r,depth:r.depth+1})}let l=c===`run`?t[t.indexOf(`run`,s)+1]:c;if(!l||l.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(i.has(l)&&l!==`run`)return{action:`passthrough`,rule:null,reason:null};let u=Ke(l,o,r.cwd);if(!u)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let d=J(u,{...r,depth:r.depth+1,cwd:u.pkgDir??r.cwd});return d===`allow`?{action:`allow`,rule:`pnpm-filter:${o}:${l}`,reason:null}:d}const ct=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function lt(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(`-`)||ct.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?q(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 X=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`]),ut=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function dt(e,t){let n=1;for(;n<e.length;){let t=e[n];if(!t.startsWith(`-`))break;if(X.has(t)){n+=2;continue}let r=t.indexOf(`=`);if(r!==-1){let e=t.slice(0,r);if(X.has(e)){n++;continue}}if(ut.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`}:q(e.slice(n).join(` `),{...t,depth:t.depth+1})}const ft=new Set([`Read`,`Write`,`Edit`]),pt=new Set([`Glob`,`Grep`]);function mt(e,t,n){return ft.has(e)?Le(t)?.file_path??null:pt.has(e)?Re(t)?.path??n:null}function ht(e,t,r){let i=mt(e,t,r.cwd);if(i===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of n)if(t.tools.has(e)&&t.when(i,r))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of a)if(t.tools.has(e)&&t.when(i,r))return{action:`allow`,rule:t.name,reason:null};return{action:`passthrough`,rule:null,reason:`no file rule matched`}}const Z=await Me();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=Ie(Z.tool_input);e||process.exit(0),Q=q(e.command,{cwd:Z.cwd,depth:0}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=ht(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}He({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?B(`allow`):Q.action===`deny`&&B(`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
|
+
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")];
|
|
2671
2711
|
}
|
|
2672
2712
|
//#endregion
|
|
2673
2713
|
//#region src/generators/claude-settings.ts
|
|
@@ -3293,7 +3333,7 @@ async function runGenerators(ctx) {
|
|
|
3293
3333
|
function generateMigratePrompt(results, config, detected) {
|
|
3294
3334
|
const sections = [];
|
|
3295
3335
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3296
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
3336
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.43.0 repo:sync\` on ${timestamp}_\n`);
|
|
3297
3337
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3298
3338
|
if (changed.length > 0) {
|
|
3299
3339
|
const items = changed.map((r) => {
|
|
@@ -4200,6 +4240,73 @@ async function runVersionMode(executor, config) {
|
|
|
4200
4240
|
};
|
|
4201
4241
|
}
|
|
4202
4242
|
//#endregion
|
|
4243
|
+
//#region src/release/hook.ts
|
|
4244
|
+
const PostReleaseHookSchema = z.object({ postReleaseHook: z.string().optional() });
|
|
4245
|
+
/** Read the postReleaseHook command override from .tooling.json at runtime. */
|
|
4246
|
+
function loadHookCommand(executor, cwd) {
|
|
4247
|
+
const configPath = path.join(cwd, ".tooling.json");
|
|
4248
|
+
const raw = executor.readFile(configPath);
|
|
4249
|
+
if (!raw) return void 0;
|
|
4250
|
+
try {
|
|
4251
|
+
const result = PostReleaseHookSchema.safeParse(JSON.parse(raw));
|
|
4252
|
+
if (!result.success) return void 0;
|
|
4253
|
+
return result.data.postReleaseHook;
|
|
4254
|
+
} catch (_error) {
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
/**
|
|
4259
|
+
* Execute the post-release hook after a successful publish.
|
|
4260
|
+
*
|
|
4261
|
+
* Uses the `postReleaseHook` command from `.tooling.json` if configured,
|
|
4262
|
+
* otherwise curls `POST_RELEASE_HOOK_URL` with optional Bearer auth from
|
|
4263
|
+
* `POST_RELEASE_HOOK_SECRET`. Both env vars must be set by the CI workflow.
|
|
4264
|
+
*
|
|
4265
|
+
* Failures are logged as warnings — they never fail the release.
|
|
4266
|
+
*/
|
|
4267
|
+
function runPostReleaseHook(executor, config) {
|
|
4268
|
+
if (config.dryRun) {
|
|
4269
|
+
debug(config, "Skipping post-release hook (dry-run)");
|
|
4270
|
+
return;
|
|
4271
|
+
}
|
|
4272
|
+
const customCommand = loadHookCommand(executor, config.cwd);
|
|
4273
|
+
if (customCommand) {
|
|
4274
|
+
debug(config, `Running custom post-release hook: ${customCommand}`);
|
|
4275
|
+
executeHookCommand(executor, config, customCommand);
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4278
|
+
const hookUrl = process.env["POST_RELEASE_HOOK_URL"];
|
|
4279
|
+
if (!hookUrl) {
|
|
4280
|
+
debug(config, "No POST_RELEASE_HOOK_URL set — skipping post-release hook");
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
const hookSecret = process.env["POST_RELEASE_HOOK_SECRET"];
|
|
4284
|
+
const args = [
|
|
4285
|
+
"-fsS",
|
|
4286
|
+
"-X",
|
|
4287
|
+
"POST"
|
|
4288
|
+
];
|
|
4289
|
+
if (hookSecret) args.push("-H", `Authorization: Bearer ${hookSecret}`);
|
|
4290
|
+
args.push(hookUrl);
|
|
4291
|
+
debug(config, `Running post-release hook: curl ${args.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}`);
|
|
4292
|
+
try {
|
|
4293
|
+
const result = executor.exec("curl", args, { cwd: config.cwd });
|
|
4294
|
+
if (result.exitCode !== 0) log.warn(`Post-release hook failed (exit code ${String(result.exitCode)}): ${result.stderr || result.stdout}`);
|
|
4295
|
+
else log.info("Post-release hook completed successfully");
|
|
4296
|
+
} catch (error) {
|
|
4297
|
+
log.warn(`Post-release hook error: ${error instanceof Error ? error.message : String(error)}`);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
function executeHookCommand(executor, config, command) {
|
|
4301
|
+
try {
|
|
4302
|
+
const result = executor.exec("sh", ["-c", command], { cwd: config.cwd });
|
|
4303
|
+
if (result.exitCode !== 0) log.warn(`Post-release hook failed (exit code ${String(result.exitCode)}): ${result.stderr || result.stdout}`);
|
|
4304
|
+
else log.info("Post-release hook completed successfully");
|
|
4305
|
+
} catch (error) {
|
|
4306
|
+
log.warn(`Post-release hook error: ${error instanceof Error ? error.message : String(error)}`);
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
//#endregion
|
|
4203
4310
|
//#region src/release/publish.ts
|
|
4204
4311
|
/** Mode 2: publish to npm, push tags, and create Forgejo releases. */
|
|
4205
4312
|
async function runPublishMode(executor, config) {
|
|
@@ -4299,6 +4406,7 @@ async function runPublishMode(executor, config) {
|
|
|
4299
4406
|
if (forcePushResult.exitCode !== 0) log.warn(`Warning: Failed to push sliding tags: ${forcePushResult.stderr || forcePushResult.stdout}`);
|
|
4300
4407
|
else log.info(`Created sliding tags: ${slidingTags.join(", ")}`);
|
|
4301
4408
|
}
|
|
4409
|
+
runPostReleaseHook(executor, config);
|
|
4302
4410
|
return {
|
|
4303
4411
|
mode: "publish",
|
|
4304
4412
|
tags: allTags
|
|
@@ -4722,6 +4830,7 @@ async function runSimpleRelease(executor, config) {
|
|
|
4722
4830
|
}
|
|
4723
4831
|
let releaseCreated = false;
|
|
4724
4832
|
if (!config.noRelease && config.platform) releaseCreated = await createPlatformRelease(executor, config, tag);
|
|
4833
|
+
if (pushed) runPostReleaseHook(executor, config);
|
|
4725
4834
|
return {
|
|
4726
4835
|
version,
|
|
4727
4836
|
tag,
|
|
@@ -5850,7 +5959,7 @@ const forgejoSecretsCommand = defineCommand({
|
|
|
5850
5959
|
const main = defineCommand({
|
|
5851
5960
|
meta: {
|
|
5852
5961
|
name: "bst",
|
|
5853
|
-
version: "0.
|
|
5962
|
+
version: "0.43.0",
|
|
5854
5963
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
5855
5964
|
},
|
|
5856
5965
|
subCommands: {
|
|
@@ -5869,7 +5978,7 @@ const main = defineCommand({
|
|
|
5869
5978
|
"forgejo:secrets": forgejoSecretsCommand
|
|
5870
5979
|
}
|
|
5871
5980
|
});
|
|
5872
|
-
console.log(`@bensandee/tooling v0.
|
|
5981
|
+
console.log(`@bensandee/tooling v0.43.0`);
|
|
5873
5982
|
async function run() {
|
|
5874
5983
|
await runMain(main);
|
|
5875
5984
|
process.exit(process.exitCode ?? 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.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/
|
|
58
|
-
"@bensandee/
|
|
57
|
+
"@bensandee/claude-hooks": "0.6.1",
|
|
58
|
+
"@bensandee/config": "0.10.1"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|
package/tooling.schema.json
CHANGED
|
@@ -172,6 +172,13 @@
|
|
|
172
172
|
"postReleaseHook": {
|
|
173
173
|
"description": "Override the post-release hook command (default: curl POST_RELEASE_HOOK_URL secret if set)",
|
|
174
174
|
"type": "string"
|
|
175
|
+
},
|
|
176
|
+
"postReleaseHookSecrets": {
|
|
177
|
+
"description": "Additional secret names to inject as env vars in the post-release hook step (e.g. ['WEBHOOK_SECRET'])",
|
|
178
|
+
"type": "array",
|
|
179
|
+
"items": {
|
|
180
|
+
"type": "string"
|
|
181
|
+
}
|
|
175
182
|
}
|
|
176
183
|
},
|
|
177
184
|
"additionalProperties": false
|