@bensandee/tooling 0.45.0 → 0.46.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 +338 -404
- package/package.json +2 -2
package/dist/bin.mjs
CHANGED
|
@@ -4,16 +4,16 @@ import { defineCommand, runMain } from "citty";
|
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import { isCancel, select } from "@clack/prompts";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { execFileSync, execSync } from "node:child_process";
|
|
9
9
|
import JSON5 from "json5";
|
|
10
10
|
import { parse } from "jsonc-parser";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
|
|
13
13
|
import { isMap, isScalar, parse as parse$1, parseDocument, stringify, visit } from "yaml";
|
|
14
|
-
import { createHmac } from "node:crypto";
|
|
15
14
|
import picomatch from "picomatch";
|
|
16
15
|
import { tmpdir } from "node:os";
|
|
16
|
+
import { createHmac } from "node:crypto";
|
|
17
17
|
//#region src/types.ts
|
|
18
18
|
const LEGACY_TOOLS = [
|
|
19
19
|
"eslint",
|
|
@@ -1167,6 +1167,18 @@ function hasEnginesNode(ctx) {
|
|
|
1167
1167
|
function computeNodeVersionYaml(ctx) {
|
|
1168
1168
|
return hasEnginesNode(ctx) ? "node-version-file: package.json" : "node-version: \"24\"";
|
|
1169
1169
|
}
|
|
1170
|
+
/** Build the docker publish env block, omitting registry vars when derivable at runtime. */
|
|
1171
|
+
function dockerPublishEnv(registryDerivable) {
|
|
1172
|
+
return {
|
|
1173
|
+
...registryDerivable ? {} : {
|
|
1174
|
+
DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
|
|
1175
|
+
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE")
|
|
1176
|
+
},
|
|
1177
|
+
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
1178
|
+
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD"),
|
|
1179
|
+
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1170
1182
|
//#endregion
|
|
1171
1183
|
//#region src/generators/publish-ci.ts
|
|
1172
1184
|
/**
|
|
@@ -1179,18 +1191,6 @@ function hasRepositoryUrl(ctx) {
|
|
|
1179
1191
|
if (!raw) return false;
|
|
1180
1192
|
return parsePackageJson(raw)?.repository !== void 0;
|
|
1181
1193
|
}
|
|
1182
|
-
/** Build the docker publish env block, omitting registry vars when derivable at runtime. */
|
|
1183
|
-
function dockerPublishEnv$1(registryDerivable) {
|
|
1184
|
-
return {
|
|
1185
|
-
...registryDerivable ? {} : {
|
|
1186
|
-
DOCKER_REGISTRY_HOST: actionsExpr("vars.DOCKER_REGISTRY_HOST"),
|
|
1187
|
-
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE")
|
|
1188
|
-
},
|
|
1189
|
-
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
1190
|
-
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD"),
|
|
1191
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
1194
|
function publishSteps(nodeVersionYaml, registryDerivable) {
|
|
1195
1195
|
return [
|
|
1196
1196
|
{ step: { uses: "actions/checkout@v6" } },
|
|
@@ -1202,13 +1202,13 @@ function publishSteps(nodeVersionYaml, registryDerivable) {
|
|
|
1202
1202
|
{ step: { run: "pnpm install --frozen-lockfile" } },
|
|
1203
1203
|
{ step: {
|
|
1204
1204
|
name: "Publish Docker images",
|
|
1205
|
-
env: dockerPublishEnv
|
|
1205
|
+
env: dockerPublishEnv(registryDerivable),
|
|
1206
1206
|
run: "pnpm exec bst docker:publish"
|
|
1207
1207
|
} }
|
|
1208
1208
|
];
|
|
1209
1209
|
}
|
|
1210
1210
|
const DockerMapSchema = z.object({ docker: z.record(z.string(), z.unknown()).optional() });
|
|
1211
|
-
const PostReleaseHookSchema
|
|
1211
|
+
const PostReleaseHookSchema = z.object({
|
|
1212
1212
|
postReleaseHook: z.string().optional(),
|
|
1213
1213
|
postReleaseHookSecrets: z.array(z.string()).optional()
|
|
1214
1214
|
});
|
|
@@ -1216,25 +1216,37 @@ const PostReleaseHookSchema$1 = z.object({
|
|
|
1216
1216
|
function getPostReleaseHookCommand(ctx) {
|
|
1217
1217
|
const raw = ctx.read(".tooling.json");
|
|
1218
1218
|
if (!raw) return void 0;
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1219
|
+
try {
|
|
1220
|
+
const result = PostReleaseHookSchema.safeParse(JSON.parse(raw));
|
|
1221
|
+
if (!result.success) return void 0;
|
|
1222
|
+
return result.data.postReleaseHook;
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
log.warn(`Failed to parse .tooling.json: ${error instanceof Error ? error.message : error}`);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1222
1227
|
}
|
|
1223
1228
|
/** Read the postReleaseHookSecrets array from .tooling.json, if configured. */
|
|
1224
1229
|
function getPostReleaseHookSecrets(ctx) {
|
|
1225
1230
|
const raw = ctx.read(".tooling.json");
|
|
1226
1231
|
if (!raw) return void 0;
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1232
|
+
try {
|
|
1233
|
+
const result = PostReleaseHookSchema.safeParse(JSON.parse(raw));
|
|
1234
|
+
if (!result.success) return void 0;
|
|
1235
|
+
return result.data.postReleaseHookSecrets;
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
log.warn(`Failed to parse .tooling.json: ${error instanceof Error ? error.message : error}`);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1230
1240
|
}
|
|
1231
1241
|
/** Get names of packages that have Docker builds (by convention or .tooling.json config). */
|
|
1232
1242
|
function getDockerPackageNames(ctx) {
|
|
1233
1243
|
const names = [];
|
|
1234
1244
|
const configRaw = ctx.read(".tooling.json");
|
|
1235
|
-
if (configRaw) {
|
|
1245
|
+
if (configRaw) try {
|
|
1236
1246
|
const result = DockerMapSchema.safeParse(JSON.parse(configRaw));
|
|
1237
1247
|
if (result.success && result.data.docker) names.push(...Object.keys(result.data.docker));
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
log.warn(`Failed to parse .tooling.json: ${error instanceof Error ? error.message : error}`);
|
|
1238
1250
|
}
|
|
1239
1251
|
if (ctx.config.structure === "monorepo") {
|
|
1240
1252
|
const packages = getMonorepoPackages(ctx.targetDir);
|
|
@@ -1572,7 +1584,7 @@ async function generatePackageJson(ctx) {
|
|
|
1572
1584
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1573
1585
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1574
1586
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.1";
|
|
1575
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1587
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.46.0";
|
|
1576
1588
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1577
1589
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1578
1590
|
"@changesets/cli": "2.30.0",
|
|
@@ -2196,9 +2208,10 @@ function hookSecretEnv(secrets) {
|
|
|
2196
2208
|
}
|
|
2197
2209
|
/** Step that runs after release, gated on the POST_RELEASE_HOOK_URL secret being set. */
|
|
2198
2210
|
function postReleaseHookStep(opts) {
|
|
2211
|
+
const ifCondition = opts?.condition ?? (opts?.guardMain ? "github.ref == 'refs/heads/main'" : void 0);
|
|
2199
2212
|
return { step: {
|
|
2200
2213
|
name: "Post-release hook",
|
|
2201
|
-
...
|
|
2214
|
+
...ifCondition && { if: ifCondition },
|
|
2202
2215
|
env: hookSecretEnv(opts?.secrets),
|
|
2203
2216
|
run: opts?.command ?? DEFAULT_POST_RELEASE_COMMAND
|
|
2204
2217
|
} };
|
|
@@ -2270,35 +2283,25 @@ function simpleReleaseCommand() {
|
|
|
2270
2283
|
"pnpm exec bst release:simple $FLAGS"
|
|
2271
2284
|
].join("\n");
|
|
2272
2285
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
DOCKER_REGISTRY_NAMESPACE: actionsExpr("vars.DOCKER_REGISTRY_NAMESPACE")
|
|
2279
|
-
},
|
|
2280
|
-
DOCKER_USERNAME: actionsExpr("secrets.DOCKER_USERNAME"),
|
|
2281
|
-
DOCKER_PASSWORD: actionsExpr("secrets.DOCKER_PASSWORD"),
|
|
2282
|
-
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2286
|
+
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2287
|
+
const releaseEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("github.token") } : {
|
|
2288
|
+
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2289
|
+
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2290
|
+
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
2283
2291
|
};
|
|
2284
|
-
|
|
2285
|
-
function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable, hookSecrets) {
|
|
2292
|
+
const pushedIf = "steps.release.outputs.pushed == 'true'";
|
|
2286
2293
|
const releaseStep = { step: {
|
|
2294
|
+
id: "release",
|
|
2287
2295
|
name: "Release",
|
|
2288
2296
|
env: {
|
|
2289
|
-
...
|
|
2290
|
-
FORGEJO_SERVER_URL: actionsExpr("github.server_url"),
|
|
2291
|
-
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2292
|
-
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN")
|
|
2293
|
-
},
|
|
2294
|
-
...hookSecretEnv(hookSecrets),
|
|
2297
|
+
...releaseEnv,
|
|
2295
2298
|
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2296
2299
|
},
|
|
2297
2300
|
run: simpleReleaseCommand()
|
|
2298
2301
|
} };
|
|
2299
2302
|
const dockerStep = { step: {
|
|
2300
2303
|
name: "Publish Docker images",
|
|
2301
|
-
if:
|
|
2304
|
+
if: pushedIf,
|
|
2302
2305
|
env: dockerPublishEnv(registryDerivable),
|
|
2303
2306
|
run: "pnpm exec bst docker:publish"
|
|
2304
2307
|
} };
|
|
@@ -2308,10 +2311,10 @@ function simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, regist
|
|
|
2308
2311
|
...hasDocker ? [dockerStep] : []
|
|
2309
2312
|
];
|
|
2310
2313
|
}
|
|
2311
|
-
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable
|
|
2314
|
+
function buildSteps(strategy, ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable) {
|
|
2312
2315
|
switch (strategy) {
|
|
2313
2316
|
case "release-it": return releaseItSteps(ci, nodeVersionYaml, publishesNpm);
|
|
2314
|
-
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable
|
|
2317
|
+
case "simple": return simpleReleaseSteps(ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable);
|
|
2315
2318
|
default: return null;
|
|
2316
2319
|
}
|
|
2317
2320
|
}
|
|
@@ -2334,7 +2337,7 @@ async function generateReleaseCi(ctx) {
|
|
|
2334
2337
|
const hasDocker = hasDockerPackages(ctx);
|
|
2335
2338
|
const registryDerivable = hasRepositoryUrl(ctx);
|
|
2336
2339
|
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
2337
|
-
const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable
|
|
2340
|
+
const steps = buildSteps(ctx.config.releaseStrategy, ctx.config.ci, nodeVersionYaml, publishesNpm, hasDocker, registryDerivable);
|
|
2338
2341
|
if (!steps) return {
|
|
2339
2342
|
filePath,
|
|
2340
2343
|
action: "skipped",
|
|
@@ -2344,6 +2347,11 @@ async function generateReleaseCi(ctx) {
|
|
|
2344
2347
|
command: getPostReleaseHookCommand(ctx),
|
|
2345
2348
|
secrets: hookSecrets
|
|
2346
2349
|
}));
|
|
2350
|
+
else if (ctx.config.releaseStrategy === "simple") steps.push(postReleaseHookStep({
|
|
2351
|
+
command: getPostReleaseHookCommand(ctx),
|
|
2352
|
+
secrets: hookSecrets,
|
|
2353
|
+
condition: "steps.release.outputs.pushed == 'true'"
|
|
2354
|
+
}));
|
|
2347
2355
|
const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
|
|
2348
2356
|
const content = buildWorkflowYaml({
|
|
2349
2357
|
ci: ctx.config.ci,
|
|
@@ -2379,8 +2387,9 @@ async function generateReleaseCi(ctx) {
|
|
|
2379
2387
|
//#endregion
|
|
2380
2388
|
//#region src/generators/ci.ts
|
|
2381
2389
|
/** Build the release step for the check job (changesets strategy). */
|
|
2382
|
-
function changesetsReleaseStep(ci, publishesNpm
|
|
2390
|
+
function changesetsReleaseStep(ci, publishesNpm) {
|
|
2383
2391
|
if (ci === "github") return { step: {
|
|
2392
|
+
id: "release",
|
|
2384
2393
|
uses: "changesets/action@v1",
|
|
2385
2394
|
if: "github.ref == 'refs/heads/main'",
|
|
2386
2395
|
with: {
|
|
@@ -2393,6 +2402,7 @@ function changesetsReleaseStep(ci, publishesNpm, hookSecrets) {
|
|
|
2393
2402
|
}
|
|
2394
2403
|
} };
|
|
2395
2404
|
return { step: {
|
|
2405
|
+
id: "release",
|
|
2396
2406
|
name: "Release",
|
|
2397
2407
|
if: "github.ref == 'refs/heads/main'",
|
|
2398
2408
|
env: {
|
|
@@ -2400,7 +2410,6 @@ function changesetsReleaseStep(ci, publishesNpm, hookSecrets) {
|
|
|
2400
2410
|
FORGEJO_REPOSITORY: actionsExpr("github.repository"),
|
|
2401
2411
|
RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN"),
|
|
2402
2412
|
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") },
|
|
2403
|
-
...hookSecretEnv(hookSecrets),
|
|
2404
2413
|
RELEASE_DEBUG: actionsExpr("vars.RELEASE_DEBUG || 'false'")
|
|
2405
2414
|
},
|
|
2406
2415
|
run: "pnpm exec bst release:changesets"
|
|
@@ -2450,14 +2459,22 @@ async function generateCi(ctx) {
|
|
|
2450
2459
|
const publishesNpm = ctx.config.publishNpm === true;
|
|
2451
2460
|
const hookCommand = getPostReleaseHookCommand(ctx);
|
|
2452
2461
|
const hookSecrets = getPostReleaseHookSecrets(ctx);
|
|
2453
|
-
const
|
|
2462
|
+
const hasDocker = isChangesets && hasDockerPackages(ctx);
|
|
2463
|
+
const registryDerivable = hasDocker && hasRepositoryUrl(ctx);
|
|
2464
|
+
const publishedIf = "steps.release.outputs.published == 'true'";
|
|
2454
2465
|
const steps = [
|
|
2455
2466
|
...checkSteps(nodeVersionYaml, isChangesets && publishesNpm),
|
|
2456
|
-
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm
|
|
2457
|
-
...
|
|
2467
|
+
...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm)] : [],
|
|
2468
|
+
...hasDocker ? [{ step: {
|
|
2469
|
+
name: "Publish Docker images",
|
|
2470
|
+
if: publishedIf,
|
|
2471
|
+
env: dockerPublishEnv(registryDerivable),
|
|
2472
|
+
run: "pnpm exec bst docker:publish"
|
|
2473
|
+
} }] : [],
|
|
2474
|
+
...isChangesets ? [postReleaseHookStep({
|
|
2458
2475
|
command: hookCommand,
|
|
2459
2476
|
secrets: hookSecrets,
|
|
2460
|
-
|
|
2477
|
+
condition: publishedIf
|
|
2461
2478
|
})] : []
|
|
2462
2479
|
];
|
|
2463
2480
|
const content = buildWorkflowYaml({
|
|
@@ -2706,7 +2723,7 @@ function writeIfChanged(ctx, filePath, content, label) {
|
|
|
2706
2723
|
};
|
|
2707
2724
|
}
|
|
2708
2725
|
async function generateClaudeHooks(ctx) {
|
|
2709
|
-
return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.2\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport*as e from\"node:fs\";import{existsSync as t,readFileSync as n,readdirSync as r,statSync as i}from\"node:fs\";import*as a from\"node:path\";import{dirname as o,join as s,resolve as c}from\"node:path\";import{fileURLToPath as l}from\"node:url\";import{execFileSync as u}from\"node:child_process\";Object.freeze({status:`aborted`});function d(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;e<a.length;e++){let t=a[e];t in n||(n[t]=i[t].bind(n))}}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var f=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const p={};function m(e){return e&&Object.assign(p,e),p}function ee(e,t){return typeof t==`bigint`?t.toString():t}function te(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ne(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const h=Symbol(`evaluating`);function g(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==h)return r===void 0&&(r=h,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const _=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function v(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function re(e){if(v(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(v(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function ie(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function y(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function ae(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function b(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n<e.issues.length;n++)if(e.issues[n]?.continue!==!0)return!0;return!1}function x(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function S(e){return typeof e==`string`?e:e?.message}function C(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=S(e.inst?._zod.def?.error?.(e))??S(t?.error?.(e))??S(n.customError?.(e))??S(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const w=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,ee,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},oe=d(`$ZodError`,w),T=d(`$ZodError`,w,{Parent:Error}),se=(e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new f;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>C(e,a,m())));throw _(t,i?.callee),t}return o.value})(T),ce=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>C(e,a,m())));throw _(t,i?.callee),t}return o.value})(T),le=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new f;return a.issues.length?{success:!1,error:new(e??oe)(a.issues.map(e=>C(e,i,m())))}:{success:!0,data:a.value}})(T),ue=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>C(e,i,m())))}:{success:!0,data:a.value}})(T),de=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},fe=/^-?\\d+(?:\\.\\d+)?$/,pe={major:4,minor:3,patch:6},E=d(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=pe;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=b(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new f;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=b(e,t))});else{if(e.issues.length===t)continue;r||=b(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(b(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(e=>t(e,r,a))}return t(o,r,a)}}g(e,`~standard`,()=>({validate:t=>{try{let n=le(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return ue(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),me=d(`$ZodString`,(e,t)=>{E.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??de(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),he=d(`$ZodUnknown`,(e,t)=>{E.init(e,t),e._zod.parse=e=>e});function D(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...x(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function ge(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=ae(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function _e(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>D(e,n,i,t,u))):D(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const ve=d(`$ZodObject`,(e,t)=>{if(E.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=te(()=>ge(t));g(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=v,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>D(n,t,e,s,r))):D(i,t,e,s,r)}return i?_e(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),ye=d(`$ZodRecord`,(e,t)=>{E.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!re(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...x(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...x(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&fe.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>C(e,r,m())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...x(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...x(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function be(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const xe=d(`$ZodOptional`,(e,t)=>{E.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,g(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),g(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ne(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>be(t,e.value)):be(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var Se,Ce=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function we(){return new Ce}(Se=globalThis).__zod_globalRegistry??(Se.__zod_globalRegistry=we()),globalThis.__zod_globalRegistry;function Te(e,t){return new e({type:`string`,...y(t)})}function Ee(e){return new e({type:`unknown`})}const O=d(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);E.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>se(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>le(e,t,n),e.parseAsync=async(t,n)=>ce(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>ue(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>ie(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),De=d(`ZodMiniString`,(e,t)=>{me.init(e,t),O.init(e,t)});function k(e){return Te(De,e)}const Oe=d(`ZodMiniUnknown`,(e,t)=>{he.init(e,t),O.init(e,t)});function A(){return Ee(Oe)}const ke=d(`ZodMiniObject`,(e,t)=>{ve.init(e,t),O.init(e,t),g(e,`shape`,()=>t.shape)});function j(e,t){return new ke({type:`object`,shape:e??{},...y(t)})}const Ae=d(`ZodMiniRecord`,(e,t)=>{ye.init(e,t),O.init(e,t)});function M(e,t,n){return new Ae({type:`record`,keyType:e,valueType:t,...y(n)})}const je=d(`ZodMiniOptional`,(e,t)=>{xe.init(e,t),O.init(e,t)});function N(e){return new je({type:`optional`,innerType:e})}const Me=j({session_id:k(),transcript_path:k(),cwd:k(),permission_mode:k(),hook_event_name:k(),tool_name:k(),tool_input:M(k(),A())});async function Ne(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Me.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Pe=j({command:k(),description:N(k())}),Fe=j({file_path:k()}),Ie=j({pattern:k(),path:N(k())});function Le(e){let t=Pe.safeParse(e);return t.success?t.data:null}function Re(e){let t=Fe.safeParse(e);return t.success?t.data:null}function ze(e){let t=Ie.safeParse(e);return t.success?t.data:null}function P(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function Be(){let e=a.dirname(l(import.meta.url));return a.join(e,`bst-hook.audit.local.jsonl`)}function Ve(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let He=!1;function Ue(t){try{let n=Be();He||=(Ve(n),!0);let r={timestamp:new Date().toISOString(),...t};e.appendFileSync(n,JSON.stringify(r)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function F(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function We(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){I(t,n,`&&`),n=``,r+=2;continue}if(o===`&`&&!(r+1<e.length&&e[r+1]===`>`)&&!(r>0&&(e[r-1]===`>`||e[r-1]===`<`))){I(t,n,`&`),n=``,r++;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){I(t,n,`||`),n=``,r+=2;continue}if(o===`|`){I(t,n,`|`),n=``,r++;continue}if(o===`;`){I(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function I(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const Ge=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Ke(e,t,n=!1){if(/[\\n\\r]/.test(t))return{safe:!1,reason:`unquoted newline in command`};for(let t of e){if(t===\"`\")return{safe:!1,reason:`backtick command substitution`};if(t.includes(`$(`))return{safe:!1,reason:`dollar command substitution`};if(t.includes(\"`\"))return{safe:!1,reason:`backtick command substitution`};if(t.includes(`<(`)||t.includes(`>(`)&&!Ge.has(t))return{safe:!1,reason:`process substitution`}}if(!n)for(let t=0;t<e.length;t++){let n=e[t];if(!Ge.has(n)){if(n.includes(`<`)&&!n.includes(`<(`))return{safe:!1,reason:`input redirect`};if(n.includes(`>`)){if(n.endsWith(`>/dev/null`)||n.match(/^(&>>|&>|>>|>|1>|2>)$/)&&e[t+1]===`/dev/null`)continue;return{safe:!1,reason:`output redirect to file`}}}}return{safe:!0}}const qe=[`.sample`,`.example`,`.template`],Je=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function Ye(e){return qe.some(t=>e.endsWith(t))}function L(e){return Ye(a.basename(e))?!1:Je.test(e)}function Xe(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&L(e))}const Ze=new Set([`status`,`log`,`diff`,`show`,`blame`,`grep`,`shortlog`,`range-diff`,`describe`,`branch`,`remote`,`fetch`,`ls-files`,`ls-tree`,`ls-remote`,`show-ref`,`for-each-ref`,`rev-parse`,`rev-list`,`cat-file`,`diff-tree`,`name-rev`,`merge-base`,`check-ignore`]),Qe=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),$e=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),et=new Set([`-C`,`-c`,`--git-dir`,`--work-tree`]);function R(e){let t=e.slice(1);for(let e=0;e<t.length;e++){let n=t[e];if(et.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function tt(e){for(let t of e.slice(1))if($e.has(t)||t===`-o`||t.startsWith(`--output`)||t.startsWith(`--exec-path=`)||t.startsWith(`--pager=`)||t.startsWith(`--upload-pack=`)||t.startsWith(`--receive-pack=`)||t.startsWith(`-o=`)||t.startsWith(`--output=`))return!1;let t=R(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===`list`||t===void 0}return Ze.has(t)}function nt(e){let t=R(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];if(t===`list`||t===void 0)return!1}return Qe.has(t)}const rt=new Set([`push`,`pop`,`apply`,`show`]);function z(e){let t=R(e);if(!t)return!1;if(t===`add`)return!0;if(t===`checkout`)return e.slice(e.indexOf(`checkout`)+1).some(e=>e===`-b`||e===`-B`);if(t===`switch`)return!0;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===void 0?!0:rt.has(t)}return!1}const B=new Set(`install.i.add.remove.rm.uninstall.un.update.up.upgrade.list.ls.ll.la.why.view.info.show.outdated.audit.licenses.init.create.link.unlink.pack.publish.config.set.get.store.cache.rebuild.rb.prune.dedupe.dlx.exec.run.patch.patch-commit.recursive.-r`.split(`.`)),it=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function at(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!B.has(e))){n=e;break}return n?it.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const ot=new Set([`view`,`info`,`show`]);function st(e){let t=e[1];return t!==void 0&&ot.has(t)}function ct(e){let t=e[1],n=`This project uses pnpm.`;return t?t===`install`||t===`i`||t===`ci`?e.slice(2).some(e=>!e.startsWith(`-`))?`${n} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`:`${n} Use \\`pnpm install\\` instead of npm.`:t===`add`?`${n} Use \\`pnpm add <pkg>\\` instead of npm.`:t===`uninstall`||t===`remove`||t===`rm`||t===`un`||t===`r`?`${n} Use \\`pnpm remove <pkg>\\` instead of npm.`:t===`run`||t===`run-script`?`${n} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`:t===`test`||t===`t`||t===`tst`?`${n} Use \\`pnpm test\\` instead of npm.`:t===`start`?`${n} Use \\`pnpm start\\` instead of npm.`:t===`stop`?`${n} Use \\`pnpm stop\\` instead of npm.`:t===`restart`?`${n} Use \\`pnpm restart\\` instead of npm.`:t===`exec`?`${n} Use \\`pnpm exec <cmd>\\` instead of npm.`:t===`list`||t===`ls`||t===`ll`||t===`la`?`${n} Use \\`pnpm list\\` instead of npm.`:t===`why`?`${n} Use \\`pnpm why\\` instead of npm.`:t===`outdated`?`${n} Use \\`pnpm outdated\\` instead of npm.`:t===`audit`?`${n} Use \\`pnpm audit\\` instead of npm.`:t===`update`||t===`up`||t===`upgrade`?`${n} Use \\`pnpm update\\` instead of npm.`:t===`init`?`${n} Use \\`pnpm init\\` instead of npm.`:t===`publish`?`${n} Use \\`pnpm publish\\` instead of npm.`:t===`link`?`${n} Use \\`pnpm link\\` instead of npm.`:t===`unlink`?`${n} Use \\`pnpm unlink\\` instead of npm.`:t===`pack`?`${n} Use \\`pnpm pack\\` instead of npm.`:t===`cache`?`${n} Use \\`pnpm store\\` instead of \\`npm cache\\`.`:t===`config`||t===`set`||t===`get`?`${n} Use \\`pnpm config\\` instead of npm.`:`${n} Use the equivalent pnpm command instead of npm.`:`${n} Use \\`pnpm\\` instead of npm.`}function V(e,t){for(let n of e.slice(1)){if(n.startsWith(`-`)){if(n===`-t`||n.startsWith(`--target-directory`))return!1;continue}if(a.basename(n).startsWith(`.`)||n.startsWith(`~/`)||n===`~`)return!1;let e=a.resolve(t.cwd,n);if(e!==t.cwd&&!e.startsWith(t.cwd+a.sep))return!1}return!0}const lt=new Set([`-exec`,`-execdir`,`-ok`,`-okdir`,`-delete`,`-fprint`,`-fprint0`,`-fprintf`,`-fls`]);function ut(e){return!e.some(e=>lt.has(e))}function dt(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function ft(e){let t=e.findIndex(e=>a.basename(e)===`rm`);if(t===-1)return!1;let n=e.slice(t+1),r=n.filter(e=>e.startsWith(`-`)&&!e.startsWith(`--`)),i=n.filter(e=>e.startsWith(`--`)),o=r.some(e=>/[rR]/.test(e)),s=i.includes(`--recursive`);return o||s}function pt(e){return e.some(e=>e===`777`||e===`0777`)}function mt(e){let t=e.indexOf(`push`);if(t===-1)return!1;let n=e.slice(t+1);return n.some(e=>e===`--force`||e===`-f`&&!n.includes(`--force-with-lease`))}function ht(e){if(!(e.includes(`reset`)&&e.includes(`--hard`)))return!1;if(e.some(e=>e===`main`||e===`master`))return!0;let t=e.filter(e=>e!==`git`&&e!==`reset`&&!e.startsWith(`-`));return t.length===0||t.includes(`HEAD`)}const gt=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>ft(e),reason:\"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"},{name:`sudo`,match:/^sudo$/,action:`deny`,reason:`Don't use sudo. If you need elevated permissions, ask me to do it manually.`},{name:`git-force-push`,match:/^git$/,action:`deny`,when:e=>mt(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>ht(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>nt(e)&&!z(e),reason:`Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.`},{name:`chmod-777`,match:/^chmod$/,action:`deny`,when:e=>pt(e),reason:`Don't use 777 permissions. Use more restrictive permissions.`},{name:`env-file-read`,match:/^(cat|head|tail|less|more|grep)$/,action:`deny`,when:e=>Xe(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`npx-redirect`,match:/^npx$/,action:`deny`,reason:\"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"}],H=[{name:`vitest`,match:/^vitest$/,action:`allow`},{name:`jest`,match:/^jest$/,action:`allow`},{name:`tsc`,match:/^tsc$/,action:`allow`},{name:`tsdown`,match:/^tsdown$/,action:`allow`},{name:`oxlint`,match:/^oxlint$/,action:`allow`},{name:`oxfmt`,match:/^oxfmt$/,action:`allow`},{name:`eslint`,match:/^eslint$/,action:`allow`},{name:`prettier`,match:/^prettier$/,action:`allow`},{name:`biome`,match:/^biome$/,action:`allow`},{name:`knip`,match:/^knip$/,action:`allow`},{name:`cat`,match:/^cat$/,action:`allow`},{name:`head`,match:/^head$/,action:`allow`},{name:`tail`,match:/^tail$/,action:`allow`},{name:`grep`,match:/^grep$/,action:`allow`},{name:`ls`,match:/^ls$/,action:`allow`},{name:`wc`,match:/^wc$/,action:`allow`},{name:`uniq`,match:/^uniq$/,action:`allow`},{name:`diff`,match:/^diff$/,action:`allow`},{name:`echo`,match:/^echo$/,action:`allow`},{name:`pwd`,match:/^pwd$/,action:`allow`},{name:`which`,match:/^which$/,action:`allow`},{name:`dirname`,match:/^dirname$/,action:`allow`},{name:`basename`,match:/^basename$/,action:`allow`},{name:`realpath`,match:/^realpath$/,action:`allow`},{name:`npm`,match:/^npm$/,action:`allow`,when:e=>st(e)},{name:`test`,match:/^test$/,action:`allow`},{name:`bracket-test`,match:/^\\[$/,action:`allow`},{name:`cd`,match:/^cd$/,action:`allow`},{name:`true`,match:/^true$/,action:`allow`},{name:`colon`,match:/^:$/,action:`allow`},{name:`find`,match:/^find$/,action:`allow`,when:e=>ut(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>dt(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>tt(e)},{name:`git-safe-write`,match:/^git$/,action:`allow`,when:e=>z(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>at(e)},{name:`cp`,match:/^cp$/,action:`allow`,when:(e,t)=>V(e,t)},{name:`mkdir`,match:/^mkdir$/,action:`allow`,when:(e,t)=>V(e,t)},{name:`touch`,match:/^touch$/,action:`allow`,when:(e,t)=>V(e,t)}],_t=j({scripts:M(k(),A())});function U(e,t){let n=Tt(t);if(!n)return null;let r=n[e];if(typeof r!=`string`)return null;let i=n[`pre${e}`],a=n[`post${e}`];return{pre:typeof i==`string`?i:null,main:r,post:typeof a==`string`?a:null}}function vt(e,t,n){let r=xt(t,n);if(!r)return null;let i=U(e,r);return i?{...i,pkgDir:r}:null}function yt(e,t){let n=W(t);if(!n)return null;let r=[];for(let t of n){let n=U(e,t);n&&r.push({...n,pkgDir:t})}return r}function bt(e){let n=c(e);for(;;){if(t(s(n,`pnpm-workspace.yaml`)))return n;let e=o(n);if(e===n)return null;n=e}}function W(e){let t=St(e);if(!t)return null;let n=[];for(let a of t){if(!a.endsWith(`/*`))continue;let t=c(e,a.slice(0,-2)),o;try{o=r(t)}catch{continue}for(let e of o){let r=s(t,e);try{if(!i(r).isDirectory())continue}catch{continue}n.push(r)}}return n.length>0?n:null}function xt(e,t){let n=W(t);if(!n)return null;for(let t of n){let n=wt(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function St(e){try{let t=n(s(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),r=t.findIndex(e=>/^packages\\s*:/.test(e));if(r===-1)return null;let i=[];for(let e=r+1;e<t.length;e++){let n=t[e],r=/^\\s+-\\s+(.+)$/.exec(n);if(!r)break;i.push(r[1].trim().replace(/['\"`]/g,``))}return i.length>0?i:null}catch{return null}}const Ct=j({name:k()});function wt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=Ct.safeParse(JSON.parse(t));return r.success?r.data.name:null}catch{return null}}function Tt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=_t.safeParse(JSON.parse(t));return r.success?r.data.scripts:null}catch{return null}}function Et(e){try{return u(`git`,[`ls-files`,`--error-unmatch`,`package.json`],{cwd:e,stdio:`ignore`}),u(`git`,[`diff`,`--quiet`,`HEAD`,`--`,`package.json`],{cwd:e,stdio:`ignore`}),!0}catch{return!1}}const Dt=new Set([`LD_PRELOAD`,`LD_LIBRARY_PATH`,`DYLD_INSERT_LIBRARIES`,`NODE_OPTIONS`,`PYTHONPATH`,`PYTHONSTARTUP`,`PERL5OPT`,`PERL5LIB`,`RUBYOPT`,`RUBYLIB`,`BASH_ENV`,`ENV`,`npm_config_prefix`,`npm_config_registry`,`EDITOR`,`VISUAL`]),Ot=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function G(e){return Dt.has(e)?!0:Ot.some(t=>e.startsWith(t))}function kt(e,t){let n=F(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let i=r[1];return a.isAbsolute(i)?i:a.resolve(t.cwd,i)}function K(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=We(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=kt(e.command,o);t&&(o={...o,cwd:t})}let t=At(e.command,o);if(t.action===`deny`)return t;t.action===`passthrough`&&(i||=t),t.action===`allow`&&(a=t)}return i||(r.length===1&&a?a:{action:`allow`,rule:`all-segments`,reason:null})}function q(e,t){let n=e.pkgDir??t.cwd,r=t.insideResolvedScript??Et(n)?{...t,insideResolvedScript:!0}:t;if(e.pre){let t=K(e.pre,r);if(t.action!==`allow`)return t}let i=K(e.main,r);if(i.action!==`allow`)return i;if(e.post){let t=K(e.post,r);if(t.action!==`allow`)return t}return`allow`}function At(e,t){let n=e.trim();if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=F(e);if(!r.ok)return{action:`passthrough`,rule:null,reason:r.error};let i=r.tokens;if(i.length===0)return{action:`passthrough`,rule:null,reason:null};let o=0;for(;o<i.length&&/^\\w+=/.test(i[o]);){let e=i[o].split(`=`)[0];if(G(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};o++}if(o>=i.length)return{action:`allow`,rule:`env-only`,reason:null};i=i.slice(o);let s=Ke(i,e,t.insideResolvedScript);if(!s.safe)return{action:`passthrough`,rule:null,reason:s.reason};if(!t.insideResolvedScript)for(let e=0;e<i.length;e++){let t=i[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(i[e+1]===`/dev/null`)continue;return{action:`passthrough`,rule:null,reason:`output redirect to file`}}if(t===`<`)return{action:`passthrough`,rule:null,reason:`input redirect`}}}let c=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);i=i.filter((e,t)=>!(c.has(e)||(e===`>`||e===`>>`)&&i[t+1]===`/dev/null`||e===`/dev/null`&&t>0&&(i[t-1]===`>`||i[t-1]===`>>`)));let l=a.basename(i[0]);if(l===`command`&&i.length>1)return K(i.slice(1).join(` `),{...t,depth:t.depth+1});if(l===`env`){let e=1;for(;e<i.length&&/^\\w+=/.test(i[e]);){let t=i[e].split(`=`)[0];if(G(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=i.length?{action:`allow`,rule:`env-only`,reason:null}:K(i.slice(e).join(` `),{...t,depth:t.depth+1})}if(l===`pnpm`)return jt(i,t);if(l===`npm`)return Nt(i);if(l===`bash`||l===`sh`||l===`zsh`)return It(i,t);if(l===`xargs`)return Rt(i,t);if(l===`node`&&(i.includes(`-e`)||i.includes(`-p`)||i.includes(`--eval`)||i.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(l===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let e of gt)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`deny`,rule:e.name,reason:e.reason??null};for(let e of H)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`allow`,rule:e.name,reason:null};return t.insideResolvedScript?{action:`allow`,rule:`resolved-script`,reason:null}:{action:`passthrough`,rule:null,reason:`no rule matched: ${l}`}}function jt(e,t){let n=e.slice(1),r,i=-1,o=!1,s=!1,c;for(let a=0;a<n.length;a++){let l=n[a];if(l===`--filter`||l.startsWith(`--filter=`))return Pt(e,n,a,t);if(l===`-r`||l===`--recursive`){o=!0;continue}if(l===`-w`||l===`--workspace-root`){s=!0;continue}if(l===`-C`||l===`--dir`){c=n[++a];continue}if(l.startsWith(`-C=`)||l.startsWith(`--dir=`)){c=l.split(`=`,2)[1];continue}if(!(l.startsWith(`-`)&&!B.has(l))){r=l,i=a;break}}if(!r)return{action:`passthrough`,rule:null,reason:null};if(r===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(r===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(r===`exec`){let e=n.slice(i+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:K(e.join(` `),{...t,depth:t.depth+1})}let l;if(r===`run`){let e=n[i+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};l=e}else if(!B.has(r))l=r;else{for(let n of H)if(n.match.test(`pnpm`)&&(!n.when||n.when(e,t)))return{action:`allow`,rule:n.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}if(o)return Mt(l,t);if(s){let e=bt(t.cwd);return e?J(l,{...t,cwd:e}):{action:`passthrough`,rule:null,reason:`workspace root not found`}}if(c){let e=a.resolve(t.cwd,c);return J(l,{...t,cwd:e})}return J(l,t)}function J(e,t){let n=U(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r=q(n,{...t,depth:t.depth+1});return r===`allow`?{action:`allow`,rule:`pnpm-script:${e}`,reason:null}:r}function Mt(e,t){let n=yt(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n=q(e,{...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd});if(n!==`allow`)return n}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function Nt(e){for(let t of H)if(t.match.test(`npm`)&&(!t.when||t.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:t.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:ct(e)}}function Pt(e,t,n,r){let i=t[n],a,o;if(i.startsWith(`--filter=`))a=i.slice(9),o=n+1;else{if(a=t[n+1],!a||a.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};o=n+2}if(!a)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let s;for(let e=o;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!B.has(n))){s=n;break}}if(!s)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(s===`install`||s===`i`)return t.slice(t.indexOf(s)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(s===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(s===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(s))return{action:`allow`,rule:`pnpm`,reason:null};if(s===`exec`){let e=t.indexOf(`exec`,o),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:K(n.join(` `),{...r,depth:r.depth+1})}let c=s===`run`?t[t.indexOf(`run`,o)+1]:s;if(!c||c.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(B.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=vt(c,a,r.cwd);if(!l)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let u=q(l,{...r,depth:r.depth+1,cwd:l.pkgDir??r.cwd});return u===`allow`?{action:`allow`,rule:`pnpm-filter:${a}:${c}`,reason:null}:u}const Ft=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function It(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||Ft.has(r))return{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}}let r=e[n+1];return r?e.length>n+2?{action:`passthrough`,rule:null,reason:`shell -c with positional args`}:n===1?K(r,{...t,depth:t.depth+1}):{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}:{action:`passthrough`,rule:null,reason:`shell -c without command`}}const Y=new Set([`-I`,`-d`,`-n`,`-P`,`-s`,`-L`,`-E`,`-a`,`--max-args`,`--max-procs`,`--delimiter`,`--replace`,`--max-lines`,`--max-chars`,`--arg-file`,`--process-slot-var`]),Lt=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function Rt(e,t){let n=1;for(;n<e.length;){let t=e[n];if(!t.startsWith(`-`))break;if(Y.has(t)){n+=2;continue}let r=t.indexOf(`=`);if(r!==-1){let e=t.slice(0,r);if(Y.has(e)){n++;continue}}if(Lt.has(t)){n++;continue}return{action:`passthrough`,rule:null,reason:`xargs with unrecognised flags`}}return n>=e.length?{action:`passthrough`,rule:null,reason:`xargs without command`}:K(e.slice(n).join(` `),{...t,depth:t.depth+1})}const zt=new Set([`Read`,`Glob`,`Grep`]),X=new Set([`Write`,`Edit`]),Bt=[{name:`env-file-read`,tools:zt,action:`deny`,when:e=>L(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-write`,tools:X,action:`deny`,when:e=>L(e),reason:`Don't write to .env files. These contain secrets.`},{name:`outside-cwd-write`,tools:X,action:`deny`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n!==t.cwd&&!n.startsWith(t.cwd+a.sep)},reason:`Don't write files outside the project directory.`}],Vt=[{name:`cwd-read`,tools:zt,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}},{name:`cwd-write`,tools:X,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}}],Ht=new Set([`Read`,`Write`,`Edit`]),Ut=new Set([`Glob`,`Grep`]);function Wt(e,t,n){return Ht.has(e)?Re(t)?.file_path??null:Ut.has(e)?ze(t)?.path??n:null}function Gt(e,t,n){let r=Wt(e,t,n.cwd);if(r===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of Bt)if(t.tools.has(e)&&t.when(r,n))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of Vt)if(t.tools.has(e)&&t.when(r,n))return{action:`allow`,rule:t.name,reason:null};return{action:`passthrough`,rule:null,reason:`no file rule matched`}}const Z=await Ne();Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);let Q,$;if(Z.tool_name===`Bash`){let e=Le(Z.tool_input);e||process.exit(0),Q=K(e.command,{cwd:Z.cwd,depth:0}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=Gt(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}Ue({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?P(`allow`):Q.action===`deny`&&P(`deny`,Q.reason);export{};", "bst-hook.mjs"), writeIfChanged(ctx, GUIDE_PATH, "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that evaluates Bash commands\nand file tool operations before execution. Commands fall into three tiers:\n\n- **Denied** — blocked with a redirect message\n- **Allowed** — auto-approved, no prompt\n- **Passthrough** — falls through to Claude Code's normal permission prompt\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **rm-recursive**: Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\n- **sudo**: Don't use sudo. If you need elevated permissions, ask me to do it manually.\n- **git-force-push**: Use `git push --force-with-lease` instead of `--force`.\n- **git-reset-hard**: Don't `reset --hard` on main. Create a branch first.\n- **git-write**: Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\n\n### File tools (Read, Write, Edit, Glob, Grep)\n\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Linters & formatters**: oxlint, oxfmt, eslint, prettier, biome, knip\n- **Read-only commands**: cat, head, tail, grep, ls, wc, uniq, diff, echo, pwd, which, dirname, basename, realpath\n- **Package managers (read-only)**: npm (view, info, show only)\n- **Shell builtins**: test, bracket-test\n- **Navigation**: cd, true, colon\n- **Conditional commands**: find (without -exec, -delete), sort (without -o / --output)\n- **Git read operations**: git (status, log, diff, show, blame, branch, remote, fetch, rev-parse, etc.)\n- **Git safe writes**: git-safe-write (add, switch -c, checkout -b, stash push/pop/apply/show)\n- **pnpm (safe built-ins)**: pnpm (list, why, view, info, show, outdated, install (bare only))\n- **File operations (within project)**: cp (paths must be within project directory), mkdir (paths must be within project directory), touch (paths must be within project directory)\n\n### File tools\n\n- **Read/Glob/Grep**: Allowed within project directory\n- **Write/Edit**: Allowed within project directory\n\n## Passthrough\n\nEverything not listed above triggers Claude Code's normal permission prompt.\nCommon examples:\n\n- `pnpm add <pkg>`, `pnpm dlx <pkg>` (dependency changes)\n- `mv` (file moves)\n- `node`, `npx` scripts\n- Commands with output redirection (`> file`)\n- Any unrecognized command\n\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment must be allowed; any deny blocks the whole command.\n- **pnpm scripts**: `pnpm <script>` resolves against `package.json` (including pre/post hooks and `--filter` workspaces) and evaluates the resolved command recursively.\n- **cd propagation**: `cd dir && cmd` evaluates `cmd` relative to the new directory.\n- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n", "bst-hook-guide.md")];
|
|
2726
|
+
return [writeIfChanged(ctx, HOOK_PATH, "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.6.3\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport*as e from\"node:fs\";import{existsSync as t,readFileSync as n,readdirSync as r,statSync as i}from\"node:fs\";import*as a from\"node:path\";import{dirname as o,join as s,resolve as c}from\"node:path\";import{fileURLToPath as l}from\"node:url\";import{execFileSync as u}from\"node:child_process\";Object.freeze({status:`aborted`});function d(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;e<a.length;e++){let t=a[e];t in n||(n[t]=i[t].bind(n))}}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var f=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const p={};function m(e){return e&&Object.assign(p,e),p}function ee(e,t){return typeof t==`bigint`?t.toString():t}function te(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ne(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const re=Symbol(`evaluating`);function h(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==re)return r===void 0&&(r=re,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const ie=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function g(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ae(e){if(g(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(g(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function oe(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function _(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function se(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function v(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n<e.issues.length;n++)if(e.issues[n]?.continue!==!0)return!0;return!1}function y(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function b(e){return typeof e==`string`?e:e?.message}function x(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=b(e.inst?._zod.def?.error?.(e))??b(t?.error?.(e))??b(n.customError?.(e))??b(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const S=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,ee,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},ce=d(`$ZodError`,S),C=d(`$ZodError`,S,{Parent:Error}),le=(e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new f;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,m())));throw ie(t,i?.callee),t}return o.value})(C),ue=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,m())));throw ie(t,i?.callee),t}return o.value})(C),w=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new f;return a.issues.length?{success:!1,error:new(e??ce)(a.issues.map(e=>x(e,i,m())))}:{success:!0,data:a.value}})(C),T=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>x(e,i,m())))}:{success:!0,data:a.value}})(C),de=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},fe=/^-?\\d+(?:\\.\\d+)?$/,pe={major:4,minor:3,patch:6},E=d(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=pe;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=v(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new f;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=v(e,t))});else{if(e.issues.length===t)continue;r||=v(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(v(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new f;return o.then(e=>t(e,r,a))}return t(o,r,a)}}h(e,`~standard`,()=>({validate:t=>{try{let n=w(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return T(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),me=d(`$ZodString`,(e,t)=>{E.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??de(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),he=d(`$ZodUnknown`,(e,t)=>{E.init(e,t),e._zod.parse=e=>e});function ge(e,t,n){e.issues.length&&t.issues.push(...y(n,e.issues)),t.value[n]=e.value}const _e=d(`$ZodArray`,(e,t)=>{E.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Array.isArray(i))return n.issues.push({expected:`array`,code:`invalid_type`,input:i,inst:e}),n;n.value=Array(i.length);let a=[];for(let e=0;e<i.length;e++){let o=i[e],s=t.element._zod.run({value:o,issues:[]},r);s instanceof Promise?a.push(s.then(t=>ge(t,n,e))):ge(s,n,e)}return a.length?Promise.all(a).then(()=>n):n}});function D(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...y(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function ve(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=se(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function ye(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>D(e,n,i,t,u))):D(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const be=d(`$ZodObject`,(e,t)=>{if(E.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=te(()=>ve(t));h(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=g,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>D(n,t,e,s,r))):D(i,t,e,s,r)}return i?ye(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),xe=d(`$ZodRecord`,(e,t)=>{E.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!ae(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...y(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...y(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&fe.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>x(e,r,m())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...y(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...y(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function Se(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const Ce=d(`$ZodOptional`,(e,t)=>{E.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,h(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),h(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ne(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>Se(t,e.value)):Se(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var we,Te=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function Ee(){return new Te}(we=globalThis).__zod_globalRegistry??(we.__zod_globalRegistry=Ee()),globalThis.__zod_globalRegistry;function De(e,t){return new e({type:`string`,..._(t)})}function Oe(e){return new e({type:`unknown`})}const O=d(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);E.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>le(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>w(e,t,n),e.parseAsync=async(t,n)=>ue(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>T(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>oe(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),ke=d(`ZodMiniString`,(e,t)=>{me.init(e,t),O.init(e,t)});function k(e){return De(ke,e)}const Ae=d(`ZodMiniUnknown`,(e,t)=>{he.init(e,t),O.init(e,t)});function A(){return Oe(Ae)}const je=d(`ZodMiniArray`,(e,t)=>{_e.init(e,t),O.init(e,t)});function Me(e,t){return new je({type:`array`,element:e,..._(t)})}const Ne=d(`ZodMiniObject`,(e,t)=>{be.init(e,t),O.init(e,t),h(e,`shape`,()=>t.shape)});function j(e,t){return new Ne({type:`object`,shape:e??{},..._(t)})}const Pe=d(`ZodMiniRecord`,(e,t)=>{xe.init(e,t),O.init(e,t)});function M(e,t,n){return new Pe({type:`record`,keyType:e,valueType:t,..._(n)})}const Fe=d(`ZodMiniOptional`,(e,t)=>{Ce.init(e,t),O.init(e,t)});function N(e){return new Fe({type:`optional`,innerType:e})}const Ie=j({session_id:k(),transcript_path:k(),cwd:k(),permission_mode:k(),hook_event_name:k(),tool_name:k(),tool_input:M(k(),A())});async function Le(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Ie.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}const Re=j({command:k(),description:N(k())}),ze=j({file_path:k()}),Be=j({pattern:k(),path:N(k())});function Ve(e){let t=Re.safeParse(e);return t.success?t.data:null}function He(e){let t=ze.safeParse(e);return t.success?t.data:null}function Ue(e){let t=Be.safeParse(e);return t.success?t.data:null}function P(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function We(){let e=a.dirname(l(import.meta.url));return a.join(e,`bst-hook.audit.local.jsonl`)}function Ge(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let Ke=!1;function qe(t){try{let n=We();Ke||=(Ge(n),!0);let r={timestamp:new Date().toISOString(),...t};e.appendFileSync(n,JSON.stringify(r)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function F(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function Je(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){I(t,n,`&&`),n=``,r+=2;continue}if(o===`&`&&!(r+1<e.length&&e[r+1]===`>`)&&!(r>0&&(e[r-1]===`>`||e[r-1]===`<`))){I(t,n,`&`),n=``,r++;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){I(t,n,`||`),n=``,r+=2;continue}if(o===`|`){I(t,n,`|`),n=``,r++;continue}if(o===`;`){I(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function I(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const L=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Ye(e,t,n=!1){if(/[\\n\\r]/.test(t))return{safe:!1,reason:`unquoted newline in command`};for(let t of e){if(t===\"`\")return{safe:!1,reason:`backtick command substitution`};if(t.includes(`$(`))return{safe:!1,reason:`dollar command substitution`};if(t.includes(\"`\"))return{safe:!1,reason:`backtick command substitution`};if(t.includes(`<(`)||t.includes(`>(`)&&!L.has(t))return{safe:!1,reason:`process substitution`}}if(!n)for(let t=0;t<e.length;t++){let n=e[t];if(!L.has(n)){if(n.includes(`<`)&&!n.includes(`<(`))return{safe:!1,reason:`input redirect`};if(n.includes(`>`)){if(n.endsWith(`>/dev/null`)||n.match(/^(&>>|&>|>>|>|1>|2>)$/)&&e[t+1]===`/dev/null`)continue;return{safe:!1,reason:`output redirect to file`}}}}return{safe:!0}}const Xe=[`.sample`,`.example`,`.template`],Ze=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function Qe(e){return Xe.some(t=>e.endsWith(t))}function R(e){return Qe(a.basename(e))?!1:Ze.test(e)}function $e(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&R(e))}const et=new Set([`status`,`log`,`diff`,`show`,`blame`,`grep`,`shortlog`,`range-diff`,`describe`,`branch`,`remote`,`fetch`,`ls-files`,`ls-tree`,`ls-remote`,`show-ref`,`for-each-ref`,`rev-parse`,`rev-list`,`cat-file`,`diff-tree`,`name-rev`,`merge-base`,`check-ignore`]),tt=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),nt=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),rt=new Set([`-C`,`-c`,`--git-dir`,`--work-tree`]);function z(e){let t=e.slice(1);for(let e=0;e<t.length;e++){let n=t[e];if(rt.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function it(e){for(let t of e.slice(1))if(nt.has(t)||t===`-o`||t.startsWith(`--output`)||t.startsWith(`--exec-path=`)||t.startsWith(`--pager=`)||t.startsWith(`--upload-pack=`)||t.startsWith(`--receive-pack=`)||t.startsWith(`-o=`)||t.startsWith(`--output=`))return!1;let t=z(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===`list`||t===void 0}return et.has(t)}function at(e){let t=z(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];if(t===`list`||t===void 0)return!1}return tt.has(t)}const ot=new Set([`push`,`pop`,`apply`,`show`]);function B(e){let t=z(e);if(!t)return!1;if(t===`add`)return!0;if(t===`checkout`)return e.slice(e.indexOf(`checkout`)+1).some(e=>e===`-b`||e===`-B`);if(t===`switch`)return!0;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===void 0?!0:ot.has(t)}return!1}const V=new Set(`install.i.add.remove.rm.uninstall.un.update.up.upgrade.list.ls.ll.la.why.view.info.show.outdated.audit.licenses.init.create.link.unlink.pack.publish.config.set.get.store.cache.rebuild.rb.prune.dedupe.dlx.exec.run.patch.patch-commit.recursive.-r`.split(`.`)),st=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function ct(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!V.has(e))){n=e;break}return n?st.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const lt=new Set([`view`,`info`,`show`]);function ut(e){let t=e[1];return t!==void 0&<.has(t)}function dt(e){let t=e[1],n=`This project uses pnpm.`;return t?t===`install`||t===`i`||t===`ci`?e.slice(2).some(e=>!e.startsWith(`-`))?`${n} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`:`${n} Use \\`pnpm install\\` instead of npm.`:t===`add`?`${n} Use \\`pnpm add <pkg>\\` instead of npm.`:t===`uninstall`||t===`remove`||t===`rm`||t===`un`||t===`r`?`${n} Use \\`pnpm remove <pkg>\\` instead of npm.`:t===`run`||t===`run-script`?`${n} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`:t===`test`||t===`t`||t===`tst`?`${n} Use \\`pnpm test\\` instead of npm.`:t===`start`?`${n} Use \\`pnpm start\\` instead of npm.`:t===`stop`?`${n} Use \\`pnpm stop\\` instead of npm.`:t===`restart`?`${n} Use \\`pnpm restart\\` instead of npm.`:t===`exec`?`${n} Use \\`pnpm exec <cmd>\\` instead of npm.`:t===`list`||t===`ls`||t===`ll`||t===`la`?`${n} Use \\`pnpm list\\` instead of npm.`:t===`why`?`${n} Use \\`pnpm why\\` instead of npm.`:t===`outdated`?`${n} Use \\`pnpm outdated\\` instead of npm.`:t===`audit`?`${n} Use \\`pnpm audit\\` instead of npm.`:t===`update`||t===`up`||t===`upgrade`?`${n} Use \\`pnpm update\\` instead of npm.`:t===`init`?`${n} Use \\`pnpm init\\` instead of npm.`:t===`publish`?`${n} Use \\`pnpm publish\\` instead of npm.`:t===`link`?`${n} Use \\`pnpm link\\` instead of npm.`:t===`unlink`?`${n} Use \\`pnpm unlink\\` instead of npm.`:t===`pack`?`${n} Use \\`pnpm pack\\` instead of npm.`:t===`cache`?`${n} Use \\`pnpm store\\` instead of \\`npm cache\\`.`:t===`config`||t===`set`||t===`get`?`${n} Use \\`pnpm config\\` instead of npm.`:`${n} Use the equivalent pnpm command instead of npm.`:`${n} Use \\`pnpm\\` instead of npm.`}function H(e,t){for(let n of e.slice(1)){if(n.startsWith(`-`)){if(n===`-t`||n.startsWith(`--target-directory`))return!1;continue}if(a.basename(n).startsWith(`.`)||n.startsWith(`~/`)||n===`~`)return!1;let e=a.resolve(t.cwd,n);if(e!==t.cwd&&!e.startsWith(t.cwd+a.sep))return!1}return!0}const ft=new Set([`-exec`,`-execdir`,`-ok`,`-okdir`,`-delete`,`-fprint`,`-fprint0`,`-fprintf`,`-fls`]);function pt(e){return!e.some(e=>ft.has(e))}function mt(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function ht(e){let t=e.findIndex(e=>a.basename(e)===`rm`);if(t===-1)return!1;let n=e.slice(t+1),r=n.filter(e=>e.startsWith(`-`)&&!e.startsWith(`--`)),i=n.filter(e=>e.startsWith(`--`)),o=r.some(e=>/[rR]/.test(e)),s=i.includes(`--recursive`);return o||s}function gt(e){return e.some(e=>e===`777`||e===`0777`)}function _t(e){let t=e.indexOf(`push`);if(t===-1)return!1;let n=e.slice(t+1);return n.some(e=>e===`--force`||e===`-f`&&!n.includes(`--force-with-lease`))}function vt(e){if(!(e.includes(`reset`)&&e.includes(`--hard`)))return!1;if(e.some(e=>e===`main`||e===`master`))return!0;let t=e.filter(e=>e!==`git`&&e!==`reset`&&!e.startsWith(`-`));return t.length===0||t.includes(`HEAD`)}const yt=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>ht(e),reason:\"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"},{name:`sudo`,match:/^sudo$/,action:`deny`,reason:`Don't use sudo. If you need elevated permissions, ask me to do it manually.`},{name:`git-force-push`,match:/^git$/,action:`deny`,when:e=>_t(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>vt(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>at(e)&&!B(e),reason:`Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.`},{name:`chmod-777`,match:/^chmod$/,action:`deny`,when:e=>gt(e),reason:`Don't use 777 permissions. Use more restrictive permissions.`},{name:`env-file-read`,match:/^(cat|head|tail|less|more|grep)$/,action:`deny`,when:e=>$e(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`npx-redirect`,match:/^npx$/,action:`deny`,reason:\"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"}],U=[{name:`vitest`,match:/^vitest$/,action:`allow`},{name:`jest`,match:/^jest$/,action:`allow`},{name:`tsc`,match:/^tsc$/,action:`allow`},{name:`tsdown`,match:/^tsdown$/,action:`allow`},{name:`oxlint`,match:/^oxlint$/,action:`allow`},{name:`oxfmt`,match:/^oxfmt$/,action:`allow`},{name:`eslint`,match:/^eslint$/,action:`allow`},{name:`prettier`,match:/^prettier$/,action:`allow`},{name:`biome`,match:/^biome$/,action:`allow`},{name:`knip`,match:/^knip$/,action:`allow`},{name:`cat`,match:/^cat$/,action:`allow`},{name:`head`,match:/^head$/,action:`allow`},{name:`tail`,match:/^tail$/,action:`allow`},{name:`grep`,match:/^grep$/,action:`allow`},{name:`ls`,match:/^ls$/,action:`allow`},{name:`wc`,match:/^wc$/,action:`allow`},{name:`uniq`,match:/^uniq$/,action:`allow`},{name:`diff`,match:/^diff$/,action:`allow`},{name:`echo`,match:/^echo$/,action:`allow`},{name:`pwd`,match:/^pwd$/,action:`allow`},{name:`which`,match:/^which$/,action:`allow`},{name:`dirname`,match:/^dirname$/,action:`allow`},{name:`basename`,match:/^basename$/,action:`allow`},{name:`realpath`,match:/^realpath$/,action:`allow`},{name:`npm`,match:/^npm$/,action:`allow`,when:e=>ut(e)},{name:`test`,match:/^test$/,action:`allow`},{name:`bracket-test`,match:/^\\[$/,action:`allow`},{name:`cd`,match:/^cd$/,action:`allow`},{name:`true`,match:/^true$/,action:`allow`},{name:`colon`,match:/^:$/,action:`allow`},{name:`find`,match:/^find$/,action:`allow`,when:e=>pt(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>mt(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>it(e)},{name:`git-safe-write`,match:/^git$/,action:`allow`,when:e=>B(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>ct(e)},{name:`cp`,match:/^cp$/,action:`allow`,when:(e,t)=>H(e,t)},{name:`mkdir`,match:/^mkdir$/,action:`allow`,when:(e,t)=>H(e,t)},{name:`touch`,match:/^touch$/,action:`allow`,when:(e,t)=>H(e,t)}],W=`.claude/hooks/bst-hook.settings.json`,bt=j({allow:Me(k())});function xt(e,t){try{return u(`git`,[`ls-files`,`--error-unmatch`,t],{cwd:e,stdio:`ignore`}),u(`git`,[`diff`,`--quiet`,`HEAD`,`--`,t],{cwd:e,stdio:`ignore`}),!0}catch{return!1}}function St(e){let r=Dt(e)??e,i=s(r,W);if(!t(i)||!xt(r,W))return[];try{let e=n(i,`utf-8`),t=JSON.parse(e),r=bt.safeParse(t);return r.success?r.data.allow.filter(e=>typeof e==`string`&&e.length>0):[]}catch{return[]}}function Ct(e,t,n){let r=n.split(/\\s+/).filter(Boolean);if(r.length===0||r[0]!==e)return!1;let i=r.slice(1),a=t.slice(1);for(let e=0;e<i.length;e++)if(e>=a.length||a[e]!==i[e])return!1;return!0}const wt=j({scripts:M(k(),A())});function G(e,t){let n=Nt(t);if(!n)return null;let r=n[e];if(typeof r!=`string`)return null;let i=n[`pre${e}`],a=n[`post${e}`];return{pre:typeof i==`string`?i:null,main:r,post:typeof a==`string`?a:null}}function Tt(e,t,n){let r=kt(t,n);if(!r)return null;let i=G(e,r);return i?{...i,pkgDir:r}:null}function Et(e,t){let n=Ot(t);if(!n)return null;let r=[];for(let t of n){let n=G(e,t);n&&r.push({...n,pkgDir:t})}return r}function Dt(e){let n=c(e);for(;;){if(t(s(n,`pnpm-workspace.yaml`)))return n;let e=o(n);if(e===n)return null;n=e}}function Ot(e){let t=At(e);if(!t)return null;let n=[];for(let a of t){if(!a.endsWith(`/*`))continue;let t=c(e,a.slice(0,-2)),o;try{o=r(t)}catch{continue}for(let e of o){let r=s(t,e);try{if(!i(r).isDirectory())continue}catch{continue}n.push(r)}}return n.length>0?n:null}function kt(e,t){let n=Ot(t);if(!n)return null;for(let t of n){let n=Mt(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function At(e){try{let t=n(s(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),r=t.findIndex(e=>/^packages\\s*:/.test(e));if(r===-1)return null;let i=[];for(let e=r+1;e<t.length;e++){let n=t[e],r=/^\\s+-\\s+(.+)$/.exec(n);if(!r)break;i.push(r[1].trim().replace(/['\"`]/g,``))}return i.length>0?i:null}catch{return null}}const jt=j({name:k()});function Mt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=jt.safeParse(JSON.parse(t));return r.success?r.data.name:null}catch{return null}}function Nt(e){try{let t=n(s(e,`package.json`),`utf-8`),r=wt.safeParse(JSON.parse(t));return r.success?r.data.scripts:null}catch{return null}}function Pt(e){return xt(e,`package.json`)}const Ft=new Set([`LD_PRELOAD`,`LD_LIBRARY_PATH`,`DYLD_INSERT_LIBRARIES`,`NODE_OPTIONS`,`PYTHONPATH`,`PYTHONSTARTUP`,`PERL5OPT`,`PERL5LIB`,`RUBYOPT`,`RUBYLIB`,`BASH_ENV`,`ENV`,`npm_config_prefix`,`npm_config_registry`,`EDITOR`,`VISUAL`]),It=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function Lt(e){return Ft.has(e)?!0:It.some(t=>e.startsWith(t))}function Rt(e,t){let n=F(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let i=r[1];return a.isAbsolute(i)?i:a.resolve(t.cwd,i)}function K(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=Je(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=Rt(e.command,o);t&&(o={...o,cwd:t})}let t=zt(e.command,o);if(t.action===`deny`)return t;t.action===`passthrough`&&(i||=t),t.action===`allow`&&(a=t)}return i||(r.length===1&&a?a:{action:`allow`,rule:`all-segments`,reason:null})}function q(e,t){let n=e.pkgDir??t.cwd,r=t.insideResolvedScript??Pt(n)?{...t,insideResolvedScript:!0}:t;if(e.pre){let t=K(e.pre,r);if(t.action!==`allow`)return t}let i=K(e.main,r);if(i.action!==`allow`)return i;if(e.post){let t=K(e.post,r);if(t.action!==`allow`)return t}return`allow`}function zt(e,t){let n=e.trim();if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=F(e);if(!r.ok)return{action:`passthrough`,rule:null,reason:r.error};let i=r.tokens;if(i.length===0)return{action:`passthrough`,rule:null,reason:null};let o=0;for(;o<i.length&&/^\\w+=/.test(i[o]);){let e=i[o].split(`=`)[0];if(Lt(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};o++}if(o>=i.length)return{action:`allow`,rule:`env-only`,reason:null};i=i.slice(o);let s=Ye(i,e,t.insideResolvedScript);if(!s.safe)return{action:`passthrough`,rule:null,reason:s.reason};if(!t.insideResolvedScript)for(let e=0;e<i.length;e++){let t=i[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(i[e+1]===`/dev/null`)continue;return{action:`passthrough`,rule:null,reason:`output redirect to file`}}if(t===`<`)return{action:`passthrough`,rule:null,reason:`input redirect`}}}let c=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);i=i.filter((e,t)=>!(c.has(e)||(e===`>`||e===`>>`)&&i[t+1]===`/dev/null`||e===`/dev/null`&&t>0&&(i[t-1]===`>`||i[t-1]===`>>`)));let l=a.basename(i[0]);if(l===`command`&&i.length>1)return K(i.slice(1).join(` `),{...t,depth:t.depth+1});if(l===`env`){let e=1;for(;e<i.length&&/^\\w+=/.test(i[e]);){let t=i[e].split(`=`)[0];if(Lt(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=i.length?{action:`allow`,rule:`env-only`,reason:null}:K(i.slice(e).join(` `),{...t,depth:t.depth+1})}if(l===`pnpm`)return Bt(i,t);if(l===`npm`)return Ht(i);if(l===`bash`||l===`sh`||l===`zsh`)return Gt(i,t);if(l===`xargs`)return qt(i,t);if(l===`node`&&(i.includes(`-e`)||i.includes(`-p`)||i.includes(`--eval`)||i.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(l===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let e of yt)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`deny`,rule:e.name,reason:e.reason??null};for(let e of U)if(e.match.test(l)&&(!e.when||e.when(i,t)))return{action:`allow`,rule:e.name,reason:null};if(t.repoAllowPatterns){for(let e of t.repoAllowPatterns)if(Ct(l,i,e))return{action:`allow`,rule:`repo:${l}`,reason:null}}return t.insideResolvedScript?{action:`allow`,rule:`resolved-script`,reason:null}:{action:`passthrough`,rule:null,reason:`no rule matched: ${l}`}}function Bt(e,t){let n=e.slice(1),r,i=-1,o=!1,s=!1,c;for(let a=0;a<n.length;a++){let l=n[a];if(l===`--filter`||l.startsWith(`--filter=`))return Ut(e,n,a,t);if(l===`-r`||l===`--recursive`){o=!0;continue}if(l===`-w`||l===`--workspace-root`){s=!0;continue}if(l===`-C`||l===`--dir`){c=n[++a];continue}if(l.startsWith(`-C=`)||l.startsWith(`--dir=`)){c=l.split(`=`,2)[1];continue}if(!(l.startsWith(`-`)&&!V.has(l))){r=l,i=a;break}}if(!r)return{action:`passthrough`,rule:null,reason:null};if(r===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(r===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(r===`exec`){let e=n.slice(i+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:K(e.join(` `),{...t,depth:t.depth+1})}let l;if(r===`run`){let e=n[i+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};l=e}else if(!V.has(r))l=r;else{for(let n of U)if(n.match.test(`pnpm`)&&(!n.when||n.when(e,t)))return{action:`allow`,rule:n.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}if(o)return Vt(l,t);if(s){let e=Dt(t.cwd);return e?J(l,{...t,cwd:e}):{action:`passthrough`,rule:null,reason:`workspace root not found`}}if(c){let e=a.resolve(t.cwd,c);return J(l,{...t,cwd:e})}return J(l,t)}function J(e,t){let n=G(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r=q(n,{...t,depth:t.depth+1});return r===`allow`?{action:`allow`,rule:`pnpm-script:${e}`,reason:null}:r}function Vt(e,t){let n=Et(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n=q(e,{...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd});if(n!==`allow`)return n}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function Ht(e){for(let t of U)if(t.match.test(`npm`)&&(!t.when||t.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:t.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:dt(e)}}function Ut(e,t,n,r){let i=t[n],a,o;if(i.startsWith(`--filter=`))a=i.slice(9),o=n+1;else{if(a=t[n+1],!a||a.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};o=n+2}if(!a)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let s;for(let e=o;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!V.has(n))){s=n;break}}if(!s)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(s===`install`||s===`i`)return t.slice(t.indexOf(s)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(s===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(s===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(s))return{action:`allow`,rule:`pnpm`,reason:null};if(s===`exec`){let e=t.indexOf(`exec`,o),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:K(n.join(` `),{...r,depth:r.depth+1})}let c=s===`run`?t[t.indexOf(`run`,o)+1]:s;if(!c||c.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(V.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=Tt(c,a,r.cwd);if(!l)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let u=q(l,{...r,depth:r.depth+1,cwd:l.pkgDir??r.cwd});return u===`allow`?{action:`allow`,rule:`pnpm-filter:${a}:${c}`,reason:null}:u}const Wt=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function Gt(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||Wt.has(r))return{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}}let r=e[n+1];return r?e.length>n+2?{action:`passthrough`,rule:null,reason:`shell -c with positional args`}:n===1?K(r,{...t,depth:t.depth+1}):{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}:{action:`passthrough`,rule:null,reason:`shell -c without command`}}const Y=new Set([`-I`,`-d`,`-n`,`-P`,`-s`,`-L`,`-E`,`-a`,`--max-args`,`--max-procs`,`--delimiter`,`--replace`,`--max-lines`,`--max-chars`,`--arg-file`,`--process-slot-var`]),Kt=new Set([`-0`,`--null`,`-t`,`--verbose`,`-r`,`--no-run-if-empty`,`-p`,`--interactive`,`-x`,`--exit`,`--show-limits`]);function qt(e,t){let n=1;for(;n<e.length;){let t=e[n];if(!t.startsWith(`-`))break;if(Y.has(t)){n+=2;continue}let r=t.indexOf(`=`);if(r!==-1){let e=t.slice(0,r);if(Y.has(e)){n++;continue}}if(t.length>2&&Y.has(t.slice(0,2))){n++;continue}if(Kt.has(t)){n++;continue}return{action:`passthrough`,rule:null,reason:`xargs with unrecognised flags`}}return n>=e.length?{action:`passthrough`,rule:null,reason:`xargs without command`}:K(e.slice(n).join(` `),{...t,depth:t.depth+1})}const Jt=new Set([`Read`,`Glob`,`Grep`]),X=new Set([`Write`,`Edit`]),Yt=[{name:`env-file-read`,tools:Jt,action:`deny`,when:e=>R(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-write`,tools:X,action:`deny`,when:e=>R(e),reason:`Don't write to .env files. These contain secrets.`},{name:`hooks-folder-write`,tools:X,action:`deny`,when:(e,t)=>{let n=a.resolve(t.cwd,e),r=a.join(t.cwd,`.claude`,`hooks`);return n.startsWith(r+a.sep)||n===r},reason:`Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.`},{name:`claude-settings-write`,tools:X,action:`deny`,when:(e,t)=>a.resolve(t.cwd,e)===a.join(t.cwd,`.claude`,`settings.json`),reason:`Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.`},{name:`outside-cwd-write`,tools:X,action:`deny`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n!==t.cwd&&!n.startsWith(t.cwd+a.sep)},reason:`Don't write files outside the project directory.`}],Xt=[{name:`cwd-read`,tools:Jt,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}},{name:`cwd-write`,tools:X,action:`allow`,when:(e,t)=>{let n=a.resolve(t.cwd,e);return n===t.cwd||n.startsWith(t.cwd+a.sep)}}],Zt=new Set([`Read`,`Write`,`Edit`]),Qt=new Set([`Glob`,`Grep`]);function $t(e,t,n){return Zt.has(e)?He(t)?.file_path??null:Qt.has(e)?Ue(t)?.path??n:null}function en(e,t,n){let r=$t(e,t,n.cwd);if(r===null)return{action:`passthrough`,rule:null,reason:`unknown tool input shape`};for(let t of Yt)if(t.tools.has(e)&&t.when(r,n))return{action:`deny`,rule:t.name,reason:t.reason??null};for(let t of Xt)if(t.tools.has(e)&&t.when(r,n))return{action:`allow`,rule:t.name,reason:null};return{action:`passthrough`,rule:null,reason:`no file rule matched`}}const Z=await Le();Z.hook_event_name!==`PreToolUse`&&process.exit(0),new Set([`Bash`,`Read`,`Write`,`Edit`,`Glob`,`Grep`]).has(Z.tool_name)||process.exit(0);const tn=St(Z.cwd);let Q,$;if(Z.tool_name===`Bash`){let e=Ve(Z.tool_input);e||process.exit(0),Q=K(e.command,{cwd:Z.cwd,depth:0,repoAllowPatterns:tn}),$=e.command}else{let e=process.env.CLAUDE_PROJECT_DIR??Z.cwd;Q=en(Z.tool_name,Z.tool_input,{cwd:e});let t=String(Z.tool_input.file_path??Z.tool_input.path??``);$=`${Z.tool_name} ${t}`}qe({sessionId:Z.session_id,cwd:Z.cwd,toolName:Z.tool_name,command:$,decision:Q.action,rule:Q.rule,reason:Q.reason}),Q.action===`allow`?P(`allow`):Q.action===`deny`&&P(`deny`,Q.reason);export{};", "bst-hook.mjs"), writeIfChanged(ctx, GUIDE_PATH, "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that evaluates Bash commands\nand file tool operations before execution. Commands fall into three tiers:\n\n- **Denied** — blocked with a redirect message\n- **Allowed** — auto-approved, no prompt\n- **Passthrough** — falls through to Claude Code's normal permission prompt\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **rm-recursive**: Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\n- **sudo**: Don't use sudo. If you need elevated permissions, ask me to do it manually.\n- **git-force-push**: Use `git push --force-with-lease` instead of `--force`.\n- **git-reset-hard**: Don't `reset --hard` on main. Create a branch first.\n- **git-write**: Git write operations (push, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\n\n### File tools (Read, Write, Edit, Glob, Grep)\n\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **hooks-folder-write**: Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\n- **claude-settings-write**: Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\n- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Linters & formatters**: oxlint, oxfmt, eslint, prettier, biome, knip\n- **Read-only commands**: cat, head, tail, grep, ls, wc, uniq, diff, echo, pwd, which, dirname, basename, realpath\n- **Package managers (read-only)**: npm (view, info, show only)\n- **Shell builtins**: test, bracket-test\n- **Navigation**: cd, true, colon\n- **Conditional commands**: find (without -exec, -delete), sort (without -o / --output)\n- **Git read operations**: git (status, log, diff, show, blame, branch, remote, fetch, rev-parse, etc.)\n- **Git safe writes**: git-safe-write (add, switch -c, checkout -b, stash push/pop/apply/show)\n- **pnpm (safe built-ins)**: pnpm (list, why, view, info, show, outdated, install (bare only))\n- **File operations (within project)**: cp (paths must be within project directory), mkdir (paths must be within project directory), touch (paths must be within project directory)\n\n### File tools\n\n- **Read/Glob/Grep**: Allowed within project directory\n- **Write/Edit**: Allowed within project directory\n\n## Passthrough\n\nEverything not listed above triggers Claude Code's normal permission prompt.\nCommon examples:\n\n- `pnpm add <pkg>`, `pnpm dlx <pkg>` (dependency changes)\n- `mv` (file moves)\n- `node`, `npx` scripts\n- Commands with output redirection (`> file`)\n- Any unrecognized command\n\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment must be allowed; any deny blocks the whole command.\n- **pnpm scripts**: `pnpm <script>` resolves against `package.json` (including pre/post hooks and `--filter` workspaces) and evaluates the resolved command recursively.\n- **cd propagation**: `cd dir && cmd` evaluates `cmd` relative to the new directory.\n- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n", "bst-hook-guide.md")];
|
|
2710
2727
|
}
|
|
2711
2728
|
//#endregion
|
|
2712
2729
|
//#region src/generators/claude-settings.ts
|
|
@@ -3332,7 +3349,7 @@ async function runGenerators(ctx) {
|
|
|
3332
3349
|
function generateMigratePrompt(results, config, detected) {
|
|
3333
3350
|
const sections = [];
|
|
3334
3351
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3335
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
3352
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.46.0 repo:sync\` on ${timestamp}_\n`);
|
|
3336
3353
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3337
3354
|
if (changed.length > 0) {
|
|
3338
3355
|
const items = changed.map((r) => {
|
|
@@ -4239,6 +4256,111 @@ async function runVersionMode(executor, config) {
|
|
|
4239
4256
|
};
|
|
4240
4257
|
}
|
|
4241
4258
|
//#endregion
|
|
4259
|
+
//#region src/release/publish.ts
|
|
4260
|
+
/** Mode 2: publish to npm, push tags, and create Forgejo releases. */
|
|
4261
|
+
async function runPublishMode(executor, config) {
|
|
4262
|
+
log.info("No changesets — publishing packages");
|
|
4263
|
+
const publishResult = executor.exec("pnpm", ["changeset", "publish"], { cwd: config.cwd });
|
|
4264
|
+
debugExec(config, "pnpm changeset publish", publishResult);
|
|
4265
|
+
if (publishResult.exitCode !== 0) throw new FatalError(`pnpm changeset publish failed (exit code ${String(publishResult.exitCode)}):\n${publishResult.stderr}`);
|
|
4266
|
+
const stdoutTags = parseNewTags(publishResult.stdout + "\n" + publishResult.stderr);
|
|
4267
|
+
debug(config, `Tags from publish stdout: ${stdoutTags.length > 0 ? stdoutTags.join(", ") : "(none)"}`);
|
|
4268
|
+
const expectedTags = computeExpectedTags(executor.listWorkspacePackages(config.cwd));
|
|
4269
|
+
debug(config, `Expected tags from workspace packages: ${expectedTags.length > 0 ? expectedTags.join(", ") : "(none)"}`);
|
|
4270
|
+
const remoteTags = parseRemoteTags(executor.exec("git", [
|
|
4271
|
+
"ls-remote",
|
|
4272
|
+
"--tags",
|
|
4273
|
+
"origin"
|
|
4274
|
+
], { cwd: config.cwd }).stdout);
|
|
4275
|
+
debug(config, `Remote tags: ${remoteTags.length > 0 ? remoteTags.join(", ") : "(none)"}`);
|
|
4276
|
+
const remoteSet = new Set(remoteTags);
|
|
4277
|
+
const tagsToPush = reconcileTags(expectedTags, remoteTags, stdoutTags);
|
|
4278
|
+
debug(config, `Reconciled tags to push: ${tagsToPush.length > 0 ? tagsToPush.join(", ") : "(none)"}`);
|
|
4279
|
+
if (config.dryRun) {
|
|
4280
|
+
if (tagsToPush.length === 0) {
|
|
4281
|
+
log.info("No packages were published");
|
|
4282
|
+
return { mode: "none" };
|
|
4283
|
+
}
|
|
4284
|
+
log.info(`Tags to process: ${tagsToPush.join(", ")}`);
|
|
4285
|
+
log.info("[dry-run] Would push tags and create releases");
|
|
4286
|
+
return {
|
|
4287
|
+
mode: "publish",
|
|
4288
|
+
tags: tagsToPush
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
const conn = {
|
|
4292
|
+
serverUrl: config.serverUrl,
|
|
4293
|
+
repository: config.repository,
|
|
4294
|
+
token: config.token
|
|
4295
|
+
};
|
|
4296
|
+
const remoteExpectedTags = expectedTags.filter((t) => remoteSet.has(t) && !tagsToPush.includes(t));
|
|
4297
|
+
const tagsWithMissingReleases = [];
|
|
4298
|
+
for (const tag of remoteExpectedTags) if (!await findRelease(executor, conn, tag)) tagsWithMissingReleases.push(tag);
|
|
4299
|
+
const allTags = [...tagsToPush, ...tagsWithMissingReleases];
|
|
4300
|
+
if (allTags.length === 0) {
|
|
4301
|
+
log.info("No packages were published");
|
|
4302
|
+
return { mode: "none" };
|
|
4303
|
+
}
|
|
4304
|
+
log.info(`Tags to process: ${allTags.join(", ")}`);
|
|
4305
|
+
const errors = [];
|
|
4306
|
+
for (const tag of allTags) try {
|
|
4307
|
+
if (!remoteSet.has(tag)) {
|
|
4308
|
+
if (executor.exec("git", [
|
|
4309
|
+
"tag",
|
|
4310
|
+
"-l",
|
|
4311
|
+
tag
|
|
4312
|
+
], { cwd: config.cwd }).stdout.trim() === "") {
|
|
4313
|
+
const tagResult = executor.exec("git", ["tag", tag], { cwd: config.cwd });
|
|
4314
|
+
if (tagResult.exitCode !== 0) throw new FatalError(`Failed to create tag ${tag}: ${tagResult.stderr || tagResult.stdout}`);
|
|
4315
|
+
}
|
|
4316
|
+
const pushTagResult = executor.exec("git", [
|
|
4317
|
+
"push",
|
|
4318
|
+
"origin",
|
|
4319
|
+
`refs/tags/${tag}`
|
|
4320
|
+
], { cwd: config.cwd });
|
|
4321
|
+
if (pushTagResult.exitCode !== 0) throw new FatalError(`Failed to push tag ${tag}: ${pushTagResult.stderr || pushTagResult.stdout}`);
|
|
4322
|
+
}
|
|
4323
|
+
if (await ensureRelease(executor, conn, tag) === "exists") log.warn(`Release for ${tag} already exists — skipping`);
|
|
4324
|
+
else log.info(`Created release for ${tag}`);
|
|
4325
|
+
} catch (error) {
|
|
4326
|
+
errors.push({
|
|
4327
|
+
tag,
|
|
4328
|
+
error
|
|
4329
|
+
});
|
|
4330
|
+
log.warn(`Failed to process ${tag}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4331
|
+
}
|
|
4332
|
+
if (errors.length > 0) throw new TransientError(`Failed to create releases for: ${errors.map((e) => e.tag).join(", ")}`);
|
|
4333
|
+
const slidingTags = allTags.flatMap((t) => {
|
|
4334
|
+
const atIndex = t.lastIndexOf("@");
|
|
4335
|
+
if (atIndex <= 0) return [];
|
|
4336
|
+
const name = t.slice(0, atIndex);
|
|
4337
|
+
return computeSlidingTags(t.slice(atIndex + 1)).map((suffix) => `${name}@${suffix}`);
|
|
4338
|
+
});
|
|
4339
|
+
if (slidingTags.length > 0) {
|
|
4340
|
+
for (const tag of slidingTags) {
|
|
4341
|
+
const tagResult = executor.exec("git", [
|
|
4342
|
+
"tag",
|
|
4343
|
+
"-f",
|
|
4344
|
+
tag
|
|
4345
|
+
], { cwd: config.cwd });
|
|
4346
|
+
if (tagResult.exitCode !== 0) log.warn(`Warning: Failed to create sliding tag ${tag}: ${tagResult.stderr || tagResult.stdout}`);
|
|
4347
|
+
}
|
|
4348
|
+
const forcePushResult = executor.exec("git", [
|
|
4349
|
+
"push",
|
|
4350
|
+
"origin",
|
|
4351
|
+
...slidingTags.map((t) => `refs/tags/${t}`),
|
|
4352
|
+
"--force"
|
|
4353
|
+
], { cwd: config.cwd });
|
|
4354
|
+
debugExec(config, "force-push sliding tags", forcePushResult);
|
|
4355
|
+
if (forcePushResult.exitCode !== 0) log.warn(`Warning: Failed to push sliding tags: ${forcePushResult.stderr || forcePushResult.stdout}`);
|
|
4356
|
+
else log.info(`Created sliding tags: ${slidingTags.join(", ")}`);
|
|
4357
|
+
}
|
|
4358
|
+
return {
|
|
4359
|
+
mode: "publish",
|
|
4360
|
+
tags: allTags
|
|
4361
|
+
};
|
|
4362
|
+
}
|
|
4363
|
+
//#endregion
|
|
4242
4364
|
//#region src/release/connection.ts
|
|
4243
4365
|
const RepositorySchema = z.union([z.string(), z.object({ url: z.string() })]);
|
|
4244
4366
|
/**
|
|
@@ -4352,345 +4474,56 @@ function configureGitIdentity(executor, platform, cwd) {
|
|
|
4352
4474
|
], { cwd });
|
|
4353
4475
|
}
|
|
4354
4476
|
//#endregion
|
|
4355
|
-
//#region src/release
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
draft: false,
|
|
4366
|
-
prerelease: false,
|
|
4367
|
-
author: { login: actor }
|
|
4368
|
-
},
|
|
4369
|
-
repository: {
|
|
4370
|
-
full_name: options.repository,
|
|
4371
|
-
html_url: `${options.serverUrl}/${options.repository}`
|
|
4477
|
+
//#region src/commands/release-changesets.ts
|
|
4478
|
+
const releaseForgejoCommand = defineCommand({
|
|
4479
|
+
meta: {
|
|
4480
|
+
name: "release:changesets",
|
|
4481
|
+
description: "Changesets version/publish for Forgejo CI"
|
|
4482
|
+
},
|
|
4483
|
+
args: {
|
|
4484
|
+
"dry-run": {
|
|
4485
|
+
type: "boolean",
|
|
4486
|
+
description: "Skip push, API calls, and publishing side effects"
|
|
4372
4487
|
},
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4488
|
+
verbose: {
|
|
4489
|
+
type: "boolean",
|
|
4490
|
+
description: "Enable detailed debug logging (also enabled by TOOLING_DEBUG env var)"
|
|
4491
|
+
}
|
|
4492
|
+
},
|
|
4493
|
+
async run({ args }) {
|
|
4494
|
+
const result = await runRelease(buildReleaseConfig({
|
|
4495
|
+
dryRun: args["dry-run"] === true,
|
|
4496
|
+
verbose: args.verbose === true || isEnvVerbose()
|
|
4497
|
+
}), createRealExecutor());
|
|
4498
|
+
if (result.mode === "publish") {
|
|
4499
|
+
const outputFile = process.env["GITHUB_OUTPUT"];
|
|
4500
|
+
if (outputFile) try {
|
|
4501
|
+
appendFileSync(outputFile, "published=true\n");
|
|
4502
|
+
} catch (_error) {}
|
|
4503
|
+
}
|
|
4504
|
+
if (result.mode === "none") process.exitCode = 0;
|
|
4505
|
+
}
|
|
4506
|
+
});
|
|
4507
|
+
/** Build release config from environment / package.json and CLI flags. */
|
|
4508
|
+
function buildReleaseConfig(flags) {
|
|
4509
|
+
const resolved = resolveConnection(process.cwd());
|
|
4510
|
+
if (resolved.type !== "forgejo") throw new FatalError("release:changesets requires a Forgejo repository");
|
|
4381
4511
|
return {
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
draft: false,
|
|
4387
|
-
prerelease: options.prerelease ?? false,
|
|
4388
|
-
author: { login: actor }
|
|
4389
|
-
},
|
|
4390
|
-
repository: {
|
|
4391
|
-
full_name: repository,
|
|
4392
|
-
html_url: repository ? `${serverUrl}/${repository}` : ""
|
|
4393
|
-
},
|
|
4394
|
-
sender: { login: actor }
|
|
4512
|
+
...resolved.conn,
|
|
4513
|
+
cwd: process.cwd(),
|
|
4514
|
+
dryRun: flags.dryRun ?? false,
|
|
4515
|
+
verbose: flags.verbose ?? false
|
|
4395
4516
|
};
|
|
4396
4517
|
}
|
|
4397
|
-
/**
|
|
4398
|
-
function
|
|
4399
|
-
const repository = process.env["GITHUB_REPOSITORY"];
|
|
4518
|
+
/** Resolve the current branch from CI env vars or git. */
|
|
4519
|
+
function getCurrentBranch(executor, cwd) {
|
|
4400
4520
|
const ref = process.env["GITHUB_REF"];
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
release: {
|
|
4408
|
-
tag_name: tagName,
|
|
4409
|
-
name: tagName,
|
|
4410
|
-
draft: false,
|
|
4411
|
-
prerelease: false,
|
|
4412
|
-
author: { login: actor }
|
|
4413
|
-
},
|
|
4414
|
-
repository: {
|
|
4415
|
-
full_name: repository,
|
|
4416
|
-
html_url: `${serverUrl}/${repository}`
|
|
4417
|
-
},
|
|
4418
|
-
sender: { login: actor }
|
|
4419
|
-
};
|
|
4420
|
-
}
|
|
4421
|
-
/**
|
|
4422
|
-
* Compute the HMAC-SHA256 hex digest of data using the given secret.
|
|
4423
|
-
* Returns the signature in GitHub-style format: `sha256=<hex>`.
|
|
4424
|
-
*/
|
|
4425
|
-
function computeSignature(secret, data) {
|
|
4426
|
-
const hmac = createHmac("sha256", secret);
|
|
4427
|
-
hmac.update(data);
|
|
4428
|
-
return `sha256=${hmac.digest("hex")}`;
|
|
4429
|
-
}
|
|
4430
|
-
/**
|
|
4431
|
-
* POST to a webhook URL with GitHub-style HMAC-SHA256 authentication.
|
|
4432
|
-
*
|
|
4433
|
-
* Sends a JSON-serialized release payload (or empty string if no payload).
|
|
4434
|
-
* If a secret is provided, computes the HMAC-SHA256 signature of the body
|
|
4435
|
-
* and includes it in the `X-Hub-Signature-256` header.
|
|
4436
|
-
*
|
|
4437
|
-
* Never throws — returns a result object indicating success or failure.
|
|
4438
|
-
*/
|
|
4439
|
-
async function sendWebhook(options) {
|
|
4440
|
-
const { url, secret, payload, fetcher = globalThis.fetch } = options;
|
|
4441
|
-
const body = payload ? JSON.stringify(payload) : "";
|
|
4442
|
-
const headers = { "Content-Type": "application/json" };
|
|
4443
|
-
if (secret) headers["X-Hub-Signature-256"] = computeSignature(secret, body);
|
|
4444
|
-
try {
|
|
4445
|
-
const response = await fetcher(url, {
|
|
4446
|
-
method: "POST",
|
|
4447
|
-
headers,
|
|
4448
|
-
body
|
|
4449
|
-
});
|
|
4450
|
-
if (!response.ok) return {
|
|
4451
|
-
ok: false,
|
|
4452
|
-
status: response.status,
|
|
4453
|
-
error: `HTTP ${String(response.status)}: ${await response.text().catch(() => "")}`
|
|
4454
|
-
};
|
|
4455
|
-
return {
|
|
4456
|
-
ok: true,
|
|
4457
|
-
status: response.status
|
|
4458
|
-
};
|
|
4459
|
-
} catch (error) {
|
|
4460
|
-
return {
|
|
4461
|
-
ok: false,
|
|
4462
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4463
|
-
};
|
|
4464
|
-
}
|
|
4465
|
-
}
|
|
4466
|
-
/**
|
|
4467
|
-
* Send a webhook and log the result. Intended for CLI usage.
|
|
4468
|
-
* Never throws.
|
|
4469
|
-
*/
|
|
4470
|
-
async function sendWebhookWithLogging(url, secret, payload) {
|
|
4471
|
-
const result = await sendWebhook({
|
|
4472
|
-
url,
|
|
4473
|
-
secret,
|
|
4474
|
-
payload
|
|
4475
|
-
});
|
|
4476
|
-
if (result.ok) log.info("Post-release hook completed successfully");
|
|
4477
|
-
else log.warn(`Post-release hook failed: ${result.error ?? "unknown error"}`);
|
|
4478
|
-
}
|
|
4479
|
-
//#endregion
|
|
4480
|
-
//#region src/release/hook.ts
|
|
4481
|
-
const PostReleaseHookSchema = z.object({ postReleaseHook: z.string().optional() });
|
|
4482
|
-
/** Read the postReleaseHook command override from .tooling.json at runtime. */
|
|
4483
|
-
function loadHookCommand(executor, cwd) {
|
|
4484
|
-
const configPath = path.join(cwd, ".tooling.json");
|
|
4485
|
-
const raw = executor.readFile(configPath);
|
|
4486
|
-
if (!raw) return void 0;
|
|
4487
|
-
try {
|
|
4488
|
-
const result = PostReleaseHookSchema.safeParse(JSON.parse(raw));
|
|
4489
|
-
if (!result.success) return void 0;
|
|
4490
|
-
return result.data.postReleaseHook;
|
|
4491
|
-
} catch (_error) {
|
|
4492
|
-
return;
|
|
4493
|
-
}
|
|
4494
|
-
}
|
|
4495
|
-
/**
|
|
4496
|
-
* Execute the post-release hook after a successful publish.
|
|
4497
|
-
*
|
|
4498
|
-
* Uses the `postReleaseHook` command from `.tooling.json` if configured,
|
|
4499
|
-
* otherwise POSTs to `POST_RELEASE_HOOK_URL` with HMAC-SHA256 authentication
|
|
4500
|
-
* using `WEBHOOK_SECRET`. Both env vars must be set by the CI workflow.
|
|
4501
|
-
*
|
|
4502
|
-
* Failures are logged as warnings — they never fail the release.
|
|
4503
|
-
*/
|
|
4504
|
-
async function runPostReleaseHook(executor, config, payload) {
|
|
4505
|
-
if (config.dryRun) {
|
|
4506
|
-
debug(config, "Skipping post-release hook (dry-run)");
|
|
4507
|
-
return;
|
|
4508
|
-
}
|
|
4509
|
-
const customCommand = loadHookCommand(executor, config.cwd);
|
|
4510
|
-
if (customCommand) {
|
|
4511
|
-
debug(config, `Running custom post-release hook: ${customCommand}`);
|
|
4512
|
-
executeHookCommand(executor, config, customCommand);
|
|
4513
|
-
return;
|
|
4514
|
-
}
|
|
4515
|
-
const hookUrl = process.env["POST_RELEASE_HOOK_URL"];
|
|
4516
|
-
if (!hookUrl) {
|
|
4517
|
-
debug(config, "No POST_RELEASE_HOOK_URL set — skipping post-release hook");
|
|
4518
|
-
return;
|
|
4519
|
-
}
|
|
4520
|
-
const hookSecret = process.env["WEBHOOK_SECRET"];
|
|
4521
|
-
debug(config, `Running post-release hook: POST ${hookUrl}`);
|
|
4522
|
-
const result = await sendWebhook({
|
|
4523
|
-
url: hookUrl,
|
|
4524
|
-
secret: hookSecret || void 0,
|
|
4525
|
-
payload,
|
|
4526
|
-
fetcher: executor.fetch.bind(executor)
|
|
4527
|
-
});
|
|
4528
|
-
if (result.ok) log.info("Post-release hook completed successfully");
|
|
4529
|
-
else log.warn(`Post-release hook failed: ${result.error ?? "unknown error"}`);
|
|
4530
|
-
}
|
|
4531
|
-
function executeHookCommand(executor, config, command) {
|
|
4532
|
-
try {
|
|
4533
|
-
const result = executor.exec("sh", ["-c", command], { cwd: config.cwd });
|
|
4534
|
-
if (result.exitCode !== 0) log.warn(`Post-release hook failed (exit code ${String(result.exitCode)}): ${result.stderr || result.stdout}`);
|
|
4535
|
-
else log.info("Post-release hook completed successfully");
|
|
4536
|
-
} catch (error) {
|
|
4537
|
-
log.warn(`Post-release hook error: ${error instanceof Error ? error.message : String(error)}`);
|
|
4538
|
-
}
|
|
4539
|
-
}
|
|
4540
|
-
//#endregion
|
|
4541
|
-
//#region src/release/publish.ts
|
|
4542
|
-
/** Mode 2: publish to npm, push tags, and create Forgejo releases. */
|
|
4543
|
-
async function runPublishMode(executor, config) {
|
|
4544
|
-
log.info("No changesets — publishing packages");
|
|
4545
|
-
const publishResult = executor.exec("pnpm", ["changeset", "publish"], { cwd: config.cwd });
|
|
4546
|
-
debugExec(config, "pnpm changeset publish", publishResult);
|
|
4547
|
-
if (publishResult.exitCode !== 0) throw new FatalError(`pnpm changeset publish failed (exit code ${String(publishResult.exitCode)}):\n${publishResult.stderr}`);
|
|
4548
|
-
const stdoutTags = parseNewTags(publishResult.stdout + "\n" + publishResult.stderr);
|
|
4549
|
-
debug(config, `Tags from publish stdout: ${stdoutTags.length > 0 ? stdoutTags.join(", ") : "(none)"}`);
|
|
4550
|
-
const expectedTags = computeExpectedTags(executor.listWorkspacePackages(config.cwd));
|
|
4551
|
-
debug(config, `Expected tags from workspace packages: ${expectedTags.length > 0 ? expectedTags.join(", ") : "(none)"}`);
|
|
4552
|
-
const remoteTags = parseRemoteTags(executor.exec("git", [
|
|
4553
|
-
"ls-remote",
|
|
4554
|
-
"--tags",
|
|
4555
|
-
"origin"
|
|
4556
|
-
], { cwd: config.cwd }).stdout);
|
|
4557
|
-
debug(config, `Remote tags: ${remoteTags.length > 0 ? remoteTags.join(", ") : "(none)"}`);
|
|
4558
|
-
const remoteSet = new Set(remoteTags);
|
|
4559
|
-
const tagsToPush = reconcileTags(expectedTags, remoteTags, stdoutTags);
|
|
4560
|
-
debug(config, `Reconciled tags to push: ${tagsToPush.length > 0 ? tagsToPush.join(", ") : "(none)"}`);
|
|
4561
|
-
if (config.dryRun) {
|
|
4562
|
-
if (tagsToPush.length === 0) {
|
|
4563
|
-
log.info("No packages were published");
|
|
4564
|
-
return { mode: "none" };
|
|
4565
|
-
}
|
|
4566
|
-
log.info(`Tags to process: ${tagsToPush.join(", ")}`);
|
|
4567
|
-
log.info("[dry-run] Would push tags and create releases");
|
|
4568
|
-
return {
|
|
4569
|
-
mode: "publish",
|
|
4570
|
-
tags: tagsToPush
|
|
4571
|
-
};
|
|
4572
|
-
}
|
|
4573
|
-
const conn = {
|
|
4574
|
-
serverUrl: config.serverUrl,
|
|
4575
|
-
repository: config.repository,
|
|
4576
|
-
token: config.token
|
|
4577
|
-
};
|
|
4578
|
-
const remoteExpectedTags = expectedTags.filter((t) => remoteSet.has(t) && !tagsToPush.includes(t));
|
|
4579
|
-
const tagsWithMissingReleases = [];
|
|
4580
|
-
for (const tag of remoteExpectedTags) if (!await findRelease(executor, conn, tag)) tagsWithMissingReleases.push(tag);
|
|
4581
|
-
const allTags = [...tagsToPush, ...tagsWithMissingReleases];
|
|
4582
|
-
if (allTags.length === 0) {
|
|
4583
|
-
log.info("No packages were published");
|
|
4584
|
-
return { mode: "none" };
|
|
4585
|
-
}
|
|
4586
|
-
log.info(`Tags to process: ${allTags.join(", ")}`);
|
|
4587
|
-
const errors = [];
|
|
4588
|
-
for (const tag of allTags) try {
|
|
4589
|
-
if (!remoteSet.has(tag)) {
|
|
4590
|
-
if (executor.exec("git", [
|
|
4591
|
-
"tag",
|
|
4592
|
-
"-l",
|
|
4593
|
-
tag
|
|
4594
|
-
], { cwd: config.cwd }).stdout.trim() === "") {
|
|
4595
|
-
const tagResult = executor.exec("git", ["tag", tag], { cwd: config.cwd });
|
|
4596
|
-
if (tagResult.exitCode !== 0) throw new FatalError(`Failed to create tag ${tag}: ${tagResult.stderr || tagResult.stdout}`);
|
|
4597
|
-
}
|
|
4598
|
-
const pushTagResult = executor.exec("git", [
|
|
4599
|
-
"push",
|
|
4600
|
-
"origin",
|
|
4601
|
-
`refs/tags/${tag}`
|
|
4602
|
-
], { cwd: config.cwd });
|
|
4603
|
-
if (pushTagResult.exitCode !== 0) throw new FatalError(`Failed to push tag ${tag}: ${pushTagResult.stderr || pushTagResult.stdout}`);
|
|
4604
|
-
}
|
|
4605
|
-
if (await ensureRelease(executor, conn, tag) === "exists") log.warn(`Release for ${tag} already exists — skipping`);
|
|
4606
|
-
else log.info(`Created release for ${tag}`);
|
|
4607
|
-
} catch (error) {
|
|
4608
|
-
errors.push({
|
|
4609
|
-
tag,
|
|
4610
|
-
error
|
|
4611
|
-
});
|
|
4612
|
-
log.warn(`Failed to process ${tag}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4613
|
-
}
|
|
4614
|
-
if (errors.length > 0) throw new TransientError(`Failed to create releases for: ${errors.map((e) => e.tag).join(", ")}`);
|
|
4615
|
-
const slidingTags = allTags.flatMap((t) => {
|
|
4616
|
-
const atIndex = t.lastIndexOf("@");
|
|
4617
|
-
if (atIndex <= 0) return [];
|
|
4618
|
-
const name = t.slice(0, atIndex);
|
|
4619
|
-
return computeSlidingTags(t.slice(atIndex + 1)).map((suffix) => `${name}@${suffix}`);
|
|
4620
|
-
});
|
|
4621
|
-
if (slidingTags.length > 0) {
|
|
4622
|
-
for (const tag of slidingTags) {
|
|
4623
|
-
const tagResult = executor.exec("git", [
|
|
4624
|
-
"tag",
|
|
4625
|
-
"-f",
|
|
4626
|
-
tag
|
|
4627
|
-
], { cwd: config.cwd });
|
|
4628
|
-
if (tagResult.exitCode !== 0) log.warn(`Warning: Failed to create sliding tag ${tag}: ${tagResult.stderr || tagResult.stdout}`);
|
|
4629
|
-
}
|
|
4630
|
-
const forcePushResult = executor.exec("git", [
|
|
4631
|
-
"push",
|
|
4632
|
-
"origin",
|
|
4633
|
-
...slidingTags.map((t) => `refs/tags/${t}`),
|
|
4634
|
-
"--force"
|
|
4635
|
-
], { cwd: config.cwd });
|
|
4636
|
-
debugExec(config, "force-push sliding tags", forcePushResult);
|
|
4637
|
-
if (forcePushResult.exitCode !== 0) log.warn(`Warning: Failed to push sliding tags: ${forcePushResult.stderr || forcePushResult.stdout}`);
|
|
4638
|
-
else log.info(`Created sliding tags: ${slidingTags.join(", ")}`);
|
|
4639
|
-
}
|
|
4640
|
-
await runPostReleaseHook(executor, config, buildChangesetsPayload({
|
|
4641
|
-
tags: allTags,
|
|
4642
|
-
repository: config.repository,
|
|
4643
|
-
serverUrl: config.serverUrl
|
|
4644
|
-
}));
|
|
4645
|
-
return {
|
|
4646
|
-
mode: "publish",
|
|
4647
|
-
tags: allTags
|
|
4648
|
-
};
|
|
4649
|
-
}
|
|
4650
|
-
//#endregion
|
|
4651
|
-
//#region src/commands/release-changesets.ts
|
|
4652
|
-
const releaseForgejoCommand = defineCommand({
|
|
4653
|
-
meta: {
|
|
4654
|
-
name: "release:changesets",
|
|
4655
|
-
description: "Changesets version/publish for Forgejo CI"
|
|
4656
|
-
},
|
|
4657
|
-
args: {
|
|
4658
|
-
"dry-run": {
|
|
4659
|
-
type: "boolean",
|
|
4660
|
-
description: "Skip push, API calls, and publishing side effects"
|
|
4661
|
-
},
|
|
4662
|
-
verbose: {
|
|
4663
|
-
type: "boolean",
|
|
4664
|
-
description: "Enable detailed debug logging (also enabled by TOOLING_DEBUG env var)"
|
|
4665
|
-
}
|
|
4666
|
-
},
|
|
4667
|
-
async run({ args }) {
|
|
4668
|
-
if ((await runRelease(buildReleaseConfig({
|
|
4669
|
-
dryRun: args["dry-run"] === true,
|
|
4670
|
-
verbose: args.verbose === true || isEnvVerbose()
|
|
4671
|
-
}), createRealExecutor())).mode === "none") process.exitCode = 0;
|
|
4672
|
-
}
|
|
4673
|
-
});
|
|
4674
|
-
/** Build release config from environment / package.json and CLI flags. */
|
|
4675
|
-
function buildReleaseConfig(flags) {
|
|
4676
|
-
const resolved = resolveConnection(process.cwd());
|
|
4677
|
-
if (resolved.type !== "forgejo") throw new FatalError("release:changesets requires a Forgejo repository");
|
|
4678
|
-
return {
|
|
4679
|
-
...resolved.conn,
|
|
4680
|
-
cwd: process.cwd(),
|
|
4681
|
-
dryRun: flags.dryRun ?? false,
|
|
4682
|
-
verbose: flags.verbose ?? false
|
|
4683
|
-
};
|
|
4684
|
-
}
|
|
4685
|
-
/** Resolve the current branch from CI env vars or git. */
|
|
4686
|
-
function getCurrentBranch(executor, cwd) {
|
|
4687
|
-
const ref = process.env["GITHUB_REF"];
|
|
4688
|
-
if (ref?.startsWith("refs/heads/")) return ref.slice(11);
|
|
4689
|
-
return executor.exec("git", [
|
|
4690
|
-
"rev-parse",
|
|
4691
|
-
"--abbrev-ref",
|
|
4692
|
-
"HEAD"
|
|
4693
|
-
], { cwd }).stdout.trim();
|
|
4521
|
+
if (ref?.startsWith("refs/heads/")) return ref.slice(11);
|
|
4522
|
+
return executor.exec("git", [
|
|
4523
|
+
"rev-parse",
|
|
4524
|
+
"--abbrev-ref",
|
|
4525
|
+
"HEAD"
|
|
4526
|
+
], { cwd }).stdout.trim();
|
|
4694
4527
|
}
|
|
4695
4528
|
/** Core release logic — testable with a mock executor. */
|
|
4696
4529
|
async function runRelease(config, executor) {
|
|
@@ -4958,16 +4791,6 @@ async function runSimpleRelease(executor, config) {
|
|
|
4958
4791
|
}
|
|
4959
4792
|
let releaseCreated = false;
|
|
4960
4793
|
if (!config.noRelease && config.platform) releaseCreated = await createPlatformRelease(executor, config, tag);
|
|
4961
|
-
if (pushed) {
|
|
4962
|
-
const conn = config.platform?.type === "forgejo" ? config.platform.conn : void 0;
|
|
4963
|
-
await runPostReleaseHook(executor, config, buildSimplePayload({
|
|
4964
|
-
tag,
|
|
4965
|
-
repository: conn?.repository,
|
|
4966
|
-
serverUrl: conn?.serverUrl,
|
|
4967
|
-
prerelease: Boolean(config.prerelease),
|
|
4968
|
-
platform: config.platform?.type
|
|
4969
|
-
}));
|
|
4970
|
-
}
|
|
4971
4794
|
return {
|
|
4972
4795
|
version,
|
|
4973
4796
|
tag,
|
|
@@ -5066,7 +4889,12 @@ const releaseSimpleCommand = defineCommand({
|
|
|
5066
4889
|
prerelease: args.prerelease,
|
|
5067
4890
|
platform
|
|
5068
4891
|
};
|
|
5069
|
-
await runSimpleRelease(createRealExecutor(), config)
|
|
4892
|
+
if ((await runSimpleRelease(createRealExecutor(), config)).pushed) {
|
|
4893
|
+
const outputFile = process.env["GITHUB_OUTPUT"];
|
|
4894
|
+
if (outputFile) try {
|
|
4895
|
+
appendFileSync(outputFile, "pushed=true\n");
|
|
4896
|
+
} catch (_error) {}
|
|
4897
|
+
}
|
|
5070
4898
|
}
|
|
5071
4899
|
});
|
|
5072
4900
|
//#endregion
|
|
@@ -5076,13 +4904,13 @@ const CHECKS = [
|
|
|
5076
4904
|
{ name: "typecheck" },
|
|
5077
4905
|
{ name: "lint" },
|
|
5078
4906
|
{ name: "test" },
|
|
5079
|
-
{ name: "docker:build" },
|
|
5080
4907
|
{
|
|
5081
4908
|
name: "format",
|
|
5082
4909
|
args: "--check"
|
|
5083
4910
|
},
|
|
5084
4911
|
{ name: "knip" },
|
|
5085
4912
|
{ name: "tooling:check" },
|
|
4913
|
+
{ name: "docker:build" },
|
|
5086
4914
|
{ name: "docker:check" }
|
|
5087
4915
|
];
|
|
5088
4916
|
/** Check if a name matches any skip pattern. Supports glob syntax via picomatch. */
|
|
@@ -5816,7 +5644,8 @@ async function runSetupSecrets(executor, provider, config) {
|
|
|
5816
5644
|
const dockerPackages = detectDockerPackages(executor, config.cwd, rootName);
|
|
5817
5645
|
if (dockerPackages.length > 0) {
|
|
5818
5646
|
log.info(`Detected ${String(dockerPackages.length)} Docker package(s) — setting Docker secrets`);
|
|
5819
|
-
const
|
|
5647
|
+
const repoOwner = config.repository.split("/")[0] ?? repoName;
|
|
5648
|
+
const dockerUser = config.dockerCredentials?.username ?? repoOwner;
|
|
5820
5649
|
const dockerPass = config.dockerCredentials?.password ?? config.releaseToken;
|
|
5821
5650
|
if (!config.dockerCredentials) log.info(`Using defaults for Docker secrets: username="${dockerUser}", password=<release token>`);
|
|
5822
5651
|
if (config.dryRun) {
|
|
@@ -6092,6 +5921,111 @@ const forgejoSecretsCommand = defineCommand({
|
|
|
6092
5921
|
}
|
|
6093
5922
|
});
|
|
6094
5923
|
//#endregion
|
|
5924
|
+
//#region src/release/webhook.ts
|
|
5925
|
+
/** Build a webhook payload from simple release context. */
|
|
5926
|
+
function buildSimplePayload(options) {
|
|
5927
|
+
const repository = options.repository ?? "";
|
|
5928
|
+
const serverUrl = options.serverUrl ?? "https://github.com";
|
|
5929
|
+
const actor = options.actor ?? ciActorName(options.platform ?? "github");
|
|
5930
|
+
return {
|
|
5931
|
+
action: "published",
|
|
5932
|
+
release: {
|
|
5933
|
+
tag_name: options.tag,
|
|
5934
|
+
name: options.tag,
|
|
5935
|
+
draft: false,
|
|
5936
|
+
prerelease: options.prerelease ?? false,
|
|
5937
|
+
author: { login: actor }
|
|
5938
|
+
},
|
|
5939
|
+
repository: {
|
|
5940
|
+
full_name: repository,
|
|
5941
|
+
html_url: repository ? `${serverUrl}/${repository}` : ""
|
|
5942
|
+
},
|
|
5943
|
+
sender: { login: actor }
|
|
5944
|
+
};
|
|
5945
|
+
}
|
|
5946
|
+
/** Build a webhook payload from GitHub Actions environment variables. */
|
|
5947
|
+
function buildPayloadFromEnv() {
|
|
5948
|
+
const repository = process.env["GITHUB_REPOSITORY"];
|
|
5949
|
+
const ref = process.env["GITHUB_REF"];
|
|
5950
|
+
const serverUrl = process.env["GITHUB_SERVER_URL"] ?? "https://github.com";
|
|
5951
|
+
const actor = process.env["GITHUB_ACTOR"] ?? "github-actions[bot]";
|
|
5952
|
+
if (!repository || !ref) return void 0;
|
|
5953
|
+
const tagName = ref.startsWith("refs/tags/") ? ref.slice(10) : ref;
|
|
5954
|
+
return {
|
|
5955
|
+
action: "published",
|
|
5956
|
+
release: {
|
|
5957
|
+
tag_name: tagName,
|
|
5958
|
+
name: tagName,
|
|
5959
|
+
draft: false,
|
|
5960
|
+
prerelease: false,
|
|
5961
|
+
author: { login: actor }
|
|
5962
|
+
},
|
|
5963
|
+
repository: {
|
|
5964
|
+
full_name: repository,
|
|
5965
|
+
html_url: `${serverUrl}/${repository}`
|
|
5966
|
+
},
|
|
5967
|
+
sender: { login: actor }
|
|
5968
|
+
};
|
|
5969
|
+
}
|
|
5970
|
+
/**
|
|
5971
|
+
* Compute the HMAC-SHA256 hex digest of data using the given secret.
|
|
5972
|
+
* Returns the signature in GitHub-style format: `sha256=<hex>`.
|
|
5973
|
+
*/
|
|
5974
|
+
function computeSignature(secret, data) {
|
|
5975
|
+
const hmac = createHmac("sha256", secret);
|
|
5976
|
+
hmac.update(data);
|
|
5977
|
+
return `sha256=${hmac.digest("hex")}`;
|
|
5978
|
+
}
|
|
5979
|
+
/**
|
|
5980
|
+
* POST to a webhook URL with GitHub-style HMAC-SHA256 authentication.
|
|
5981
|
+
*
|
|
5982
|
+
* Sends a JSON-serialized release payload (or empty string if no payload).
|
|
5983
|
+
* If a secret is provided, computes the HMAC-SHA256 signature of the body
|
|
5984
|
+
* and includes it in the `X-Hub-Signature-256` header.
|
|
5985
|
+
*
|
|
5986
|
+
* Never throws — returns a result object indicating success or failure.
|
|
5987
|
+
*/
|
|
5988
|
+
async function sendWebhook(options) {
|
|
5989
|
+
const { url, secret, payload, fetcher = globalThis.fetch } = options;
|
|
5990
|
+
const body = payload ? JSON.stringify(payload) : "";
|
|
5991
|
+
const headers = { "Content-Type": "application/json" };
|
|
5992
|
+
if (secret) headers["X-Hub-Signature-256"] = computeSignature(secret, body);
|
|
5993
|
+
try {
|
|
5994
|
+
const response = await fetcher(url, {
|
|
5995
|
+
method: "POST",
|
|
5996
|
+
headers,
|
|
5997
|
+
body
|
|
5998
|
+
});
|
|
5999
|
+
if (!response.ok) return {
|
|
6000
|
+
ok: false,
|
|
6001
|
+
status: response.status,
|
|
6002
|
+
error: `HTTP ${String(response.status)}: ${await response.text().catch(() => "")}`
|
|
6003
|
+
};
|
|
6004
|
+
return {
|
|
6005
|
+
ok: true,
|
|
6006
|
+
status: response.status
|
|
6007
|
+
};
|
|
6008
|
+
} catch (error) {
|
|
6009
|
+
return {
|
|
6010
|
+
ok: false,
|
|
6011
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6012
|
+
};
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
/**
|
|
6016
|
+
* Send a webhook and log the result. Intended for CLI usage.
|
|
6017
|
+
* Never throws.
|
|
6018
|
+
*/
|
|
6019
|
+
async function sendWebhookWithLogging(url, secret, payload) {
|
|
6020
|
+
const result = await sendWebhook({
|
|
6021
|
+
url,
|
|
6022
|
+
secret,
|
|
6023
|
+
payload
|
|
6024
|
+
});
|
|
6025
|
+
if (result.ok) log.info("Post-release hook completed successfully");
|
|
6026
|
+
else log.warn(`Post-release hook failed: ${result.error ?? "unknown error"}`);
|
|
6027
|
+
}
|
|
6028
|
+
//#endregion
|
|
6095
6029
|
//#region src/commands/webhook-send.ts
|
|
6096
6030
|
const webhookSendCommand = defineCommand({
|
|
6097
6031
|
meta: {
|
|
@@ -6142,7 +6076,7 @@ const webhookSendCommand = defineCommand({
|
|
|
6142
6076
|
const main = defineCommand({
|
|
6143
6077
|
meta: {
|
|
6144
6078
|
name: "bst",
|
|
6145
|
-
version: "0.
|
|
6079
|
+
version: "0.46.0",
|
|
6146
6080
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
6147
6081
|
},
|
|
6148
6082
|
subCommands: {
|
|
@@ -6162,7 +6096,7 @@ const main = defineCommand({
|
|
|
6162
6096
|
"webhook:send": webhookSendCommand
|
|
6163
6097
|
}
|
|
6164
6098
|
});
|
|
6165
|
-
console.log(`@bensandee/tooling v0.
|
|
6099
|
+
console.log(`@bensandee/tooling v0.46.0`);
|
|
6166
6100
|
async function run() {
|
|
6167
6101
|
await runMain(main);
|
|
6168
6102
|
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.46.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"prettier": "3.8.1",
|
|
55
55
|
"release-it": "19.2.4",
|
|
56
56
|
"tsdown": "0.21.6",
|
|
57
|
-
"@bensandee/claude-hooks": "0.6.
|
|
57
|
+
"@bensandee/claude-hooks": "0.6.3",
|
|
58
58
|
"@bensandee/config": "0.10.1"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|