@bensandee/tooling 0.40.1 → 0.40.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.mjs +86 -84
  2. package/package.json +2 -9
package/dist/bin.mjs CHANGED
@@ -465,10 +465,14 @@ function buildDefaultConfig(targetDir, flags) {
465
465
  }
466
466
  //#endregion
467
467
  //#region src/utils/fs.ts
468
+ /** Check if a relative path resolves within the target directory. */
469
+ function isContained(targetDir, rel) {
470
+ const resolved = path.resolve(targetDir, rel);
471
+ return resolved.startsWith(targetDir + path.sep) || resolved === targetDir;
472
+ }
468
473
  /** Verify that a relative path resolves within the target directory. */
469
474
  function assertContained(targetDir, rel) {
470
- const resolved = path.resolve(targetDir, rel);
471
- if (!resolved.startsWith(targetDir + path.sep) && resolved !== targetDir) throw new FatalError(`Path traversal detected: ${rel} resolves outside target directory`);
475
+ if (!isContained(targetDir, rel)) throw new FatalError(`Path traversal detected: ${rel} resolves outside target directory`);
472
476
  }
473
477
  /**
474
478
  * Compare file content, using parsed comparison for JSON files to ignore
@@ -511,13 +515,11 @@ function createContext(config, confirmOverwrite) {
511
515
  targetDir: config.targetDir,
512
516
  packageJson: pkgRaw ? parsePackageJson(pkgRaw) : void 0,
513
517
  exists: (rel) => {
514
- const resolved = path.resolve(config.targetDir, rel);
515
- if (!resolved.startsWith(config.targetDir + path.sep) && resolved !== config.targetDir) return false;
518
+ if (!isContained(config.targetDir, rel)) return false;
516
519
  return fileExists(config.targetDir, rel);
517
520
  },
518
521
  read: (rel) => {
519
- const resolved = path.resolve(config.targetDir, rel);
520
- if (!resolved.startsWith(config.targetDir + path.sep) && resolved !== config.targetDir) return;
522
+ if (!isContained(config.targetDir, rel)) return void 0;
521
523
  return readFile(config.targetDir, rel);
522
524
  },
523
525
  write: (rel, content) => {
@@ -773,6 +775,17 @@ function findRootDockerfile(executor, cwd) {
773
775
  context: "."
774
776
  };
775
777
  }
778
+ /** Helper to create a DockerPackage object from resolved config. */
779
+ function createDockerPackage(executor, cwd, dir, repoName, docker) {
780
+ const isRoot = dir === ".";
781
+ const { name, version } = readPackageInfo(executor, isRoot ? path.join(cwd, "package.json") : path.join(cwd, "packages", dir, "package.json"));
782
+ return {
783
+ dir,
784
+ imageName: isRoot ? stripScope(name ?? repoName) : `${repoName}-${stripScope(name ?? dir)}`,
785
+ version,
786
+ docker
787
+ };
788
+ }
776
789
  /**
777
790
  * Discover Docker packages by convention and merge with .tooling.json overrides.
778
791
  *
@@ -794,37 +807,15 @@ function detectDockerPackages(executor, cwd, repoName) {
794
807
  const convention = findConventionDockerfile(executor, cwd, dir);
795
808
  const docker = overrides[dir] ?? convention;
796
809
  if (docker) {
797
- const { name, version } = readPackageInfo(executor, path.join(cwd, "packages", dir, "package.json"));
798
- packages.push({
799
- dir,
800
- imageName: `${repoName}-${stripScope(name ?? dir)}`,
801
- version,
802
- docker
803
- });
810
+ packages.push(createDockerPackage(executor, cwd, dir, repoName, docker));
804
811
  seen.add(dir);
805
812
  }
806
813
  }
807
- for (const [dir, docker] of Object.entries(overrides)) if (!seen.has(dir)) {
808
- const { name, version } = readPackageInfo(executor, path.join(cwd, "packages", dir, "package.json"));
809
- packages.push({
810
- dir,
811
- imageName: `${repoName}-${stripScope(name ?? dir)}`,
812
- version,
813
- docker
814
- });
815
- }
814
+ for (const [dir, docker] of Object.entries(overrides)) if (!seen.has(dir)) packages.push(createDockerPackage(executor, cwd, dir, repoName, docker));
816
815
  } else {
817
816
  const convention = findRootDockerfile(executor, cwd);
818
817
  const docker = overrides["."] ?? convention;
819
- if (docker) {
820
- const { name, version } = readPackageInfo(executor, path.join(cwd, "package.json"));
821
- packages.push({
822
- dir: ".",
823
- imageName: stripScope(name ?? repoName),
824
- version,
825
- docker
826
- });
827
- }
818
+ if (docker) packages.push(createDockerPackage(executor, cwd, ".", repoName, docker));
828
819
  }
829
820
  return packages;
830
821
  }
@@ -837,13 +828,7 @@ function readSinglePackageDocker(executor, cwd, packageDir, repoName) {
837
828
  const convention = findConventionDockerfile(executor, cwd, dir);
838
829
  const docker = loadDockerMap(executor, cwd)[dir] ?? convention;
839
830
  if (!docker) throw new FatalError(`No Dockerfile found for package "${dir}" (checked convention paths and .tooling.json)`);
840
- const { name, version } = readPackageInfo(executor, path.join(cwd, "packages", dir, "package.json"));
841
- return {
842
- dir,
843
- imageName: `${repoName}-${stripScope(name ?? dir)}`,
844
- version,
845
- docker
846
- };
831
+ return createDockerPackage(executor, cwd, dir, repoName, docker);
847
832
  }
848
833
  /** Parse semver version string into major, minor, patch components. */
849
834
  function parseSemver(version) {
@@ -1347,8 +1332,8 @@ const PER_PACKAGE_DEV_DEPS = {
1347
1332
  "release-it": "19.2.4",
1348
1333
  "tsdown": "0.21.4",
1349
1334
  "typescript": "5.9.3",
1350
- "vitest": "4.1.0",
1351
- "pnpm": "10.32.1"
1335
+ "vitest": "^4.1.1",
1336
+ "pnpm": "10.33.0"
1352
1337
  }["@types/node"],
1353
1338
  tsdown: {
1354
1339
  "@changesets/cli": "2.30.0",
@@ -1363,8 +1348,8 @@ const PER_PACKAGE_DEV_DEPS = {
1363
1348
  "release-it": "19.2.4",
1364
1349
  "tsdown": "0.21.4",
1365
1350
  "typescript": "5.9.3",
1366
- "vitest": "4.1.0",
1367
- "pnpm": "10.32.1"
1351
+ "vitest": "^4.1.1",
1352
+ "pnpm": "10.33.0"
1368
1353
  }["tsdown"],
1369
1354
  typescript: {
1370
1355
  "@changesets/cli": "2.30.0",
@@ -1379,8 +1364,8 @@ const PER_PACKAGE_DEV_DEPS = {
1379
1364
  "release-it": "19.2.4",
1380
1365
  "tsdown": "0.21.4",
1381
1366
  "typescript": "5.9.3",
1382
- "vitest": "4.1.0",
1383
- "pnpm": "10.32.1"
1367
+ "vitest": "^4.1.1",
1368
+ "pnpm": "10.33.0"
1384
1369
  }["typescript"],
1385
1370
  vitest: {
1386
1371
  "@changesets/cli": "2.30.0",
@@ -1395,8 +1380,8 @@ const PER_PACKAGE_DEV_DEPS = {
1395
1380
  "release-it": "19.2.4",
1396
1381
  "tsdown": "0.21.4",
1397
1382
  "typescript": "5.9.3",
1398
- "vitest": "4.1.0",
1399
- "pnpm": "10.32.1"
1383
+ "vitest": "^4.1.1",
1384
+ "pnpm": "10.33.0"
1400
1385
  }["vitest"]
1401
1386
  };
1402
1387
  /** DevDeps that belong at the root regardless of structure. */
@@ -1414,8 +1399,8 @@ const ROOT_DEV_DEPS = {
1414
1399
  "release-it": "19.2.4",
1415
1400
  "tsdown": "0.21.4",
1416
1401
  "typescript": "5.9.3",
1417
- "vitest": "4.1.0",
1418
- "pnpm": "10.32.1"
1402
+ "vitest": "^4.1.1",
1403
+ "pnpm": "10.33.0"
1419
1404
  }["knip"],
1420
1405
  lefthook: {
1421
1406
  "@changesets/cli": "2.30.0",
@@ -1430,8 +1415,8 @@ const ROOT_DEV_DEPS = {
1430
1415
  "release-it": "19.2.4",
1431
1416
  "tsdown": "0.21.4",
1432
1417
  "typescript": "5.9.3",
1433
- "vitest": "4.1.0",
1434
- "pnpm": "10.32.1"
1418
+ "vitest": "^4.1.1",
1419
+ "pnpm": "10.33.0"
1435
1420
  }["lefthook"],
1436
1421
  oxlint: {
1437
1422
  "@changesets/cli": "2.30.0",
@@ -1446,8 +1431,8 @@ const ROOT_DEV_DEPS = {
1446
1431
  "release-it": "19.2.4",
1447
1432
  "tsdown": "0.21.4",
1448
1433
  "typescript": "5.9.3",
1449
- "vitest": "4.1.0",
1450
- "pnpm": "10.32.1"
1434
+ "vitest": "^4.1.1",
1435
+ "pnpm": "10.33.0"
1451
1436
  }["oxlint"]
1452
1437
  };
1453
1438
  /**
@@ -1487,8 +1472,8 @@ function addReleaseDeps(deps, config) {
1487
1472
  "release-it": "19.2.4",
1488
1473
  "tsdown": "0.21.4",
1489
1474
  "typescript": "5.9.3",
1490
- "vitest": "4.1.0",
1491
- "pnpm": "10.32.1"
1475
+ "vitest": "^4.1.1",
1476
+ "pnpm": "10.33.0"
1492
1477
  }["release-it"];
1493
1478
  if (config.structure === "monorepo") deps["@release-it/bumper"] = {
1494
1479
  "@changesets/cli": "2.30.0",
@@ -1503,8 +1488,8 @@ function addReleaseDeps(deps, config) {
1503
1488
  "release-it": "19.2.4",
1504
1489
  "tsdown": "0.21.4",
1505
1490
  "typescript": "5.9.3",
1506
- "vitest": "4.1.0",
1507
- "pnpm": "10.32.1"
1491
+ "vitest": "^4.1.1",
1492
+ "pnpm": "10.33.0"
1508
1493
  }["@release-it/bumper"];
1509
1494
  break;
1510
1495
  case "simple":
@@ -1521,8 +1506,8 @@ function addReleaseDeps(deps, config) {
1521
1506
  "release-it": "19.2.4",
1522
1507
  "tsdown": "0.21.4",
1523
1508
  "typescript": "5.9.3",
1524
- "vitest": "4.1.0",
1525
- "pnpm": "10.32.1"
1509
+ "vitest": "^4.1.1",
1510
+ "pnpm": "10.33.0"
1526
1511
  }["commit-and-tag-version"];
1527
1512
  break;
1528
1513
  case "changesets":
@@ -1539,8 +1524,8 @@ function addReleaseDeps(deps, config) {
1539
1524
  "release-it": "19.2.4",
1540
1525
  "tsdown": "0.21.4",
1541
1526
  "typescript": "5.9.3",
1542
- "vitest": "4.1.0",
1543
- "pnpm": "10.32.1"
1527
+ "vitest": "^4.1.1",
1528
+ "pnpm": "10.33.0"
1544
1529
  }["@changesets/cli"];
1545
1530
  break;
1546
1531
  }
@@ -1564,7 +1549,7 @@ async function generatePackageJson(ctx) {
1564
1549
  const devDeps = { ...ROOT_DEV_DEPS };
1565
1550
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1566
1551
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.0";
1567
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.40.1";
1552
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.40.3";
1568
1553
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
1569
1554
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
1570
1555
  "@changesets/cli": "2.30.0",
@@ -1579,8 +1564,8 @@ async function generatePackageJson(ctx) {
1579
1564
  "release-it": "19.2.4",
1580
1565
  "tsdown": "0.21.4",
1581
1566
  "typescript": "5.9.3",
1582
- "vitest": "4.1.0",
1583
- "pnpm": "10.32.1"
1567
+ "vitest": "^4.1.1",
1568
+ "pnpm": "10.33.0"
1584
1569
  }["oxfmt"];
1585
1570
  if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
1586
1571
  "@changesets/cli": "2.30.0",
@@ -1595,8 +1580,8 @@ async function generatePackageJson(ctx) {
1595
1580
  "release-it": "19.2.4",
1596
1581
  "tsdown": "0.21.4",
1597
1582
  "typescript": "5.9.3",
1598
- "vitest": "4.1.0",
1599
- "pnpm": "10.32.1"
1583
+ "vitest": "^4.1.1",
1584
+ "pnpm": "10.33.0"
1600
1585
  }["prettier"];
1601
1586
  addReleaseDeps(devDeps, ctx.config);
1602
1587
  if (existing) {
@@ -1679,8 +1664,8 @@ async function generatePackageJson(ctx) {
1679
1664
  "release-it": "19.2.4",
1680
1665
  "tsdown": "0.21.4",
1681
1666
  "typescript": "5.9.3",
1682
- "vitest": "4.1.0",
1683
- "pnpm": "10.32.1"
1667
+ "vitest": "^4.1.1",
1668
+ "pnpm": "10.33.0"
1684
1669
  }["pnpm"]}`
1685
1670
  };
1686
1671
  ctx.write(filePath, JSON.stringify(pkg, null, 2) + "\n");
@@ -2406,7 +2391,7 @@ async function generatePnpmWorkspace(ctx) {
2406
2391
  //#region src/generators/claude-hooks.ts
2407
2392
  const HOOK_PATH = ".claude/hooks/bst-hook.mjs";
2408
2393
  async function generateClaudeHooks(ctx) {
2409
- const content = "/**\n * bst-hook — Claude Code PreToolUse hook\n * @bensandee/claude-hooks v0.2.0\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync — do not edit manually.\n */\nimport*as e from\"node:fs\";import{readFileSync as t,readdirSync as n,statSync as r}from\"node:fs\";import*as i from\"node:path\";import{join as a,resolve as o}from\"node:path\";import{fileURLToPath as s}from\"node:url\";Object.freeze({status:`aborted`});function c(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;e<a.length;e++){let t=a[e];t in n||(n[t]=i[t].bind(n))}}let i=n?.Parent??Object;class a extends i{}Object.defineProperty(a,`name`,{value:e});function o(e){var t;let i=n?.Parent?new a:this;r(i,e),(t=i._zod).deferred??(t.deferred=[]);for(let e of i._zod.deferred)e();return i}return Object.defineProperty(o,`init`,{value:r}),Object.defineProperty(o,Symbol.hasInstance,{value:t=>n?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var l=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}};const u={};function d(e){return e&&Object.assign(u,e),u}function ee(e,t){return typeof t==`bigint`?t.toString():t}function te(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ne(e){let t=e.startsWith(`^`)?1:0,n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}const f=Symbol(`evaluating`);function p(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==f)return r===void 0&&(r=f,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}const m=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function h(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function re(e){if(h(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(h(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function ie(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function g(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error(\"Cannot specify both `message` and `error` params\");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function _(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}-Number.MAX_VALUE,Number.MAX_VALUE;function v(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n<e.issues.length;n++)if(e.issues[n]?.continue!==!0)return!0;return!1}function y(e,t){return t.map(t=>{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function b(e){return typeof e==`string`?e:e?.message}function x(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=b(e.inst?._zod.def?.error?.(e))??b(t?.error?.(e))??b(n.customError?.(e))??b(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}const S=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,ee,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},ae=c(`$ZodError`,S),C=c(`$ZodError`,S,{Parent:Error}),w=(e=>(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new l;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,d())));throw m(t,i?.callee),t}return o.value})(C),oe=(e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>x(e,a,d())));throw m(t,i?.callee),t}return o.value})(C),T=(e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new l;return a.issues.length?{success:!1,error:new(e??ae)(a.issues.map(e=>x(e,i,d())))}:{success:!0,data:a.value}})(C),E=(e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>x(e,i,d())))}:{success:!0,data:a.value}})(C),se=e=>{let t=e?`[\\\\s\\\\S]{${e?.minimum??0},${e?.maximum??``}}`:`[\\\\s\\\\S]*`;return RegExp(`^${t}$`)},ce=/^-?\\d+(?:\\.\\d+)?$/,le={major:4,minor:3,patch:6},D=c(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=le;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=v(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new l;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=v(e,t))});else{if(e.issues.length===t)continue;r||=v(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(v(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new l;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new l;return o.then(e=>t(e,r,a))}return t(o,r,a)}}p(e,`~standard`,()=>({validate:t=>{try{let n=T(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return E(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),ue=c(`$ZodString`,(e,t)=>{D.init(e,t),e._zod.pattern=[...e?._zod.bag?.patterns??[]].pop()??se(e._zod.bag),e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=String(n.value)}catch{}return typeof n.value==`string`||n.issues.push({expected:`string`,code:`invalid_type`,input:n.value,inst:e}),n}}),de=c(`$ZodUnknown`,(e,t)=>{D.init(e,t),e._zod.parse=e=>e});function O(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...y(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function fe(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key \"${n}\": expected a Zod schema`);let n=_(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function pe(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>O(e,n,i,t,u))):O(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}const me=c(`$ZodObject`,(e,t)=>{if(D.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=te(()=>fe(t));p(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=h,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>O(n,t,e,s,r))):O(i,t,e,s,r)}return i?pe(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),he=c(`$ZodRecord`,(e,t)=>{D.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!re(i))return n.issues.push({expected:`record`,code:`invalid_type`,input:i,inst:e}),n;let a=[],o=t.keyType._zod.values;if(o){n.value={};let s=new Set;for(let e of o)if(typeof e==`string`||typeof e==`number`||typeof e==`symbol`){s.add(typeof e==`number`?e.toString():e);let o=t.valueType._zod.run({value:i[e],issues:[]},r);o instanceof Promise?a.push(o.then(t=>{t.issues.length&&n.issues.push(...y(e,t.issues)),n.value[e]=t.value})):(o.issues.length&&n.issues.push(...y(e,o.issues)),n.value[e]=o.value)}let c;for(let e in i)s.has(e)||(c??=[],c.push(e));c&&c.length>0&&n.issues.push({code:`unrecognized_keys`,input:i,inst:e,keys:c})}else{n.value={};for(let o of Reflect.ownKeys(i)){if(o===`__proto__`)continue;let s=t.keyType._zod.run({value:o,issues:[]},r);if(s instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);if(typeof o==`string`&&ce.test(o)&&s.issues.length){let e=t.keyType._zod.run({value:Number(o),issues:[]},r);if(e instanceof Promise)throw Error(`Async schemas not supported in object keys currently`);e.issues.length===0&&(s=e)}if(s.issues.length){t.mode===`loose`?n.value[o]=i[o]:n.issues.push({code:`invalid_key`,origin:`record`,issues:s.issues.map(e=>x(e,r,d())),input:o,path:[o],inst:e});continue}let c=t.valueType._zod.run({value:i[o],issues:[]},r);c instanceof Promise?a.push(c.then(e=>{e.issues.length&&n.issues.push(...y(o,e.issues)),n.value[s.value]=e.value})):(c.issues.length&&n.issues.push(...y(o,c.issues)),n.value[s.value]=c.value)}}return a.length?Promise.all(a).then(()=>n):n}});function k(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}const ge=c(`$ZodOptional`,(e,t)=>{D.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,p(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),p(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${ne(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>k(t,e.value)):k(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}});var A,_e=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function j(){return new _e}(A=globalThis).__zod_globalRegistry??(A.__zod_globalRegistry=j()),globalThis.__zod_globalRegistry;function M(e,t){return new e({type:`string`,...g(t)})}function N(e){return new e({type:`unknown`})}const P=c(`ZodMiniType`,(e,t)=>{if(!e._zod)throw Error(`Uninitialized schema in ZodMiniType.`);D.init(e,t),e.def=t,e.type=t.type,e.parse=(t,n)=>w(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>T(e,t,n),e.parseAsync=async(t,n)=>oe(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>E(e,t,n),e.check=(...n)=>e.clone({...t,checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]},{parent:!0}),e.with=e.check,e.clone=(t,n)=>ie(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.apply=t=>t(e)}),F=c(`ZodMiniString`,(e,t)=>{ue.init(e,t),P.init(e,t)});function I(e){return M(F,e)}const ve=c(`ZodMiniUnknown`,(e,t)=>{de.init(e,t),P.init(e,t)});function ye(){return N(ve)}const be=c(`ZodMiniObject`,(e,t)=>{me.init(e,t),P.init(e,t),p(e,`shape`,()=>t.shape)});function L(e,t){return new be({type:`object`,shape:e??{},...g(t)})}const xe=c(`ZodMiniRecord`,(e,t)=>{he.init(e,t),P.init(e,t)});function Se(e,t,n){return new xe({type:`record`,keyType:e,valueType:t,...g(n)})}const Ce=c(`ZodMiniOptional`,(e,t)=>{ge.init(e,t),P.init(e,t)});function we(e){return new Ce({type:`optional`,innerType:e})}const Te=L({session_id:I(),transcript_path:I(),cwd:I(),permission_mode:I(),hook_event_name:I(),tool_name:I(),tool_input:L({command:I(),description:we(I())})});async function Ee(){let e=[];for await(let t of process.stdin)e.push(typeof t==`string`?t:String(t));let t=JSON.parse(e.join(``)),n=Te.safeParse(t);return n.success||(process.stderr.write(`[bst-hook] invalid hook input: ${n.error.message}\\n`),process.exit(0)),n.data}function R(e,t){let n={hookSpecificOutput:{hookEventName:`PreToolUse`,permissionDecision:e,...e===`deny`&&t?{permissionDecisionReason:t}:{}}};process.stdout.write(JSON.stringify(n))}function De(){let e=i.dirname(s(import.meta.url));return i.join(e,`bst-hook.audit.local.jsonl`)}function Oe(t){try{if(e.statSync(t).size>1048576){let n=`${t}.1`;e.renameSync(t,n)}}catch{}}let ke=!1;function Ae(t){try{let n=De();ke||=(Oe(n),!0);let r={timestamp:new Date().toISOString(),...t};e.appendFileSync(n,JSON.stringify(r)+`\n`)}catch(e){process.stderr.write(`[bst-hook] audit write failed: ${e instanceof Error?e.message:String(e)}\\n`)}}function z(e){let t=[],n=``,r=0,i=!1;for(;r<e.length;){let a=e[r];if(a===`'`){for(i=!0,r++;r<e.length&&e[r]!==`'`;)n+=e[r],r++;if(r>=e.length)return{ok:!1,error:`unterminated single quote`};r++}else if(a===`\"`){for(i=!0,r++;r<e.length&&e[r]!==`\"`;){if(e[r]===`\\\\`&&r+1<e.length){let t=e[r+1];if(t===`\"`||t===`\\\\`||t===`$`||t===\"`\"){n+=t,r+=2;continue}}n+=e[r],r++}if(r>=e.length)return{ok:!1,error:`unterminated double quote`};r++}else a===`\\\\`&&r+1<e.length?(i=!0,n+=e[r+1],r+=2):a===` `||a===` `?(i&&=(t.push(n),n=``,!1),r++):(i=!0,n+=a,r++)}return i&&t.push(n),{ok:!0,tokens:t}}function je(e){if(!e||!e.trim())return[];let t=[],n=``,r=0,i=!1,a=!1;for(;r<e.length;){let o=e[r];if(o===`'`&&!a){i=!i,n+=o,r++;continue}if(o===`\"`&&!i){a=!a,n+=o,r++;continue}if(o===`\\\\`&&!i&&r+1<e.length){n+=o+e[r+1],r+=2;continue}if(!i&&!a){if(o===`&`&&r+1<e.length&&e[r+1]===`&`){B(t,n,`&&`),n=``,r+=2;continue}if(o===`|`&&r+1<e.length&&e[r+1]===`|`){B(t,n,`||`),n=``,r+=2;continue}if(o===`|`){B(t,n,`|`),n=``,r++;continue}if(o===`;`){B(t,n,`;`),n=``,r++;continue}}n+=o,r++}let o=n.trim();return o&&t.push({command:o,operator:null}),t}function B(e,t,n){let r=t.trim();r&&e.push({command:r,operator:n})}const V=new Set([`2>&1`,`2>/dev/null`,`>/dev/null`,`1>/dev/null`]);function Me(e,t){if(/[\\n\\r]/.test(t))return{safe:!1,reason:`unquoted newline in command`};for(let t of e){if(t===\"`\")return{safe:!1,reason:`backtick command substitution`};if(t.includes(`$(`))return{safe:!1,reason:`dollar command substitution`};if(t.includes(\"`\"))return{safe:!1,reason:`backtick command substitution`};if(t.includes(`<(`)||t.includes(`>(`)&&!V.has(t))return{safe:!1,reason:`process substitution`}}for(let t=0;t<e.length;t++){let n=e[t];if(!V.has(n)){if(n.includes(`<`)&&!n.includes(`<(`))return{safe:!1,reason:`input redirect`};if(n.includes(`>`)){if(n.endsWith(`>/dev/null`)||n.match(/^(&>>|&>|>>|>|1>|2>)$/)&&e[t+1]===`/dev/null`)continue;return{safe:!1,reason:`output redirect to file`}}}}return{safe:!0}}const Ne=new Set([`status`,`log`,`diff`,`show`,`branch`,`remote`,`fetch`,`rev-parse`]),Pe=new Set([`push`,`add`,`commit`,`reset`,`merge`,`rebase`,`cherry-pick`,`checkout`,`switch`,`stash`,`tag`,`revert`,`clean`,`rm`,`mv`]),Fe=new Set([`-c`,`--exec-path`,`--pager`,`--upload-pack`,`--receive-pack`]),Ie=new Set([`-C`,`-c`,`--git-dir`,`--work-tree`]);function H(e){let t=e.slice(1);for(let e=0;e<t.length;e++){let n=t[e];if(Ie.has(n)){e++;continue}if(!n.startsWith(`-`))return n}}function Le(e){for(let t of e.slice(1))if(Fe.has(t)||t.startsWith(`--exec-path=`)||t.startsWith(`--pager=`)||t.startsWith(`--upload-pack=`)||t.startsWith(`--receive-pack=`))return!1;let t=H(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];return t===`list`||t===void 0}return Ne.has(t)}function Re(e){let t=H(e);if(!t)return!1;if(t===`stash`){let t=e[e.indexOf(`stash`)+1];if(t===`list`||t===void 0)return!1}return Pe.has(t)}const U=new Set(`install.i.add.remove.rm.uninstall.un.update.up.upgrade.list.ls.ll.la.why.view.info.show.outdated.audit.licenses.init.create.link.unlink.pack.publish.config.set.get.store.cache.rebuild.rb.prune.dedupe.dlx.exec.run.patch.patch-commit.recursive.-r`.split(`.`)),ze=new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`]);function Be(e){let t=e.slice(1),n;for(let e of t)if(!(e.startsWith(`-`)&&!U.has(e))){n=e;break}return n?ze.has(n)?!0:n===`install`||n===`i`?t.slice(t.indexOf(n)+1).every(e=>e.startsWith(`-`)):!1:!1}const Ve=new Set([`view`,`info`,`show`]);function He(e){let t=e[1];return t!==void 0&&Ve.has(t)}function Ue(e){let t=e[1],n=`This project uses pnpm.`;return t?t===`install`||t===`i`||t===`ci`?e.slice(2).some(e=>!e.startsWith(`-`))?`${n} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`:`${n} Use \\`pnpm install\\` instead of npm.`:t===`add`?`${n} Use \\`pnpm add <pkg>\\` instead of npm.`:t===`uninstall`||t===`remove`||t===`rm`||t===`un`||t===`r`?`${n} Use \\`pnpm remove <pkg>\\` instead of npm.`:t===`run`||t===`run-script`?`${n} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`:t===`test`||t===`t`||t===`tst`?`${n} Use \\`pnpm test\\` instead of npm.`:t===`start`?`${n} Use \\`pnpm start\\` instead of npm.`:t===`stop`?`${n} Use \\`pnpm stop\\` instead of npm.`:t===`restart`?`${n} Use \\`pnpm restart\\` instead of npm.`:t===`exec`?`${n} Use \\`pnpm exec <cmd>\\` instead of npm.`:t===`list`||t===`ls`||t===`ll`||t===`la`?`${n} Use \\`pnpm list\\` instead of npm.`:t===`why`?`${n} Use \\`pnpm why\\` instead of npm.`:t===`outdated`?`${n} Use \\`pnpm outdated\\` instead of npm.`:t===`audit`?`${n} Use \\`pnpm audit\\` instead of npm.`:t===`update`||t===`up`||t===`upgrade`?`${n} Use \\`pnpm update\\` instead of npm.`:t===`init`?`${n} Use \\`pnpm init\\` instead of npm.`:t===`publish`?`${n} Use \\`pnpm publish\\` instead of npm.`:t===`link`?`${n} Use \\`pnpm link\\` instead of npm.`:t===`unlink`?`${n} Use \\`pnpm unlink\\` instead of npm.`:t===`pack`?`${n} Use \\`pnpm pack\\` instead of npm.`:t===`cache`?`${n} Use \\`pnpm store\\` instead of \\`npm cache\\`.`:t===`config`||t===`set`||t===`get`?`${n} Use \\`pnpm config\\` instead of npm.`:`${n} Use the equivalent pnpm command instead of npm.`:`${n} Use \\`pnpm\\` instead of npm.`}function W(e,t){for(let n of e.slice(1)){if(n.startsWith(`-`))continue;if(i.basename(n).startsWith(`.`)||n.startsWith(`~/`)||n===`~`)return!1;let e=i.resolve(t.cwd,n);if(e!==t.cwd&&!e.startsWith(t.cwd+i.sep))return!1}return!0}const We=new Set([`-exec`,`-execdir`,`-ok`,`-delete`]);function Ge(e){return!e.some(e=>We.has(e))}function Ke(e){return!e.includes(`-o`)&&!e.some(e=>e.startsWith(`--output`))}function qe(e){let t=e.findIndex(e=>i.basename(e)===`rm`);if(t===-1)return!1;let n=e.slice(t+1),r=n.filter(e=>e.startsWith(`-`)&&!e.startsWith(`--`)),a=n.filter(e=>e.startsWith(`--`)),o=r.some(e=>/[rR]/.test(e)),s=a.includes(`--recursive`);return o||s}function Je(e){return e.some(e=>e===`777`||e===`0777`)}const Ye=/(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;function G(e){return e.slice(1).some(e=>!e.startsWith(`-`)&&Ye.test(e))}function Xe(e){let t=e.indexOf(`push`);if(t===-1)return!1;let n=e.slice(t+1);return n.some(e=>e===`--force`||e===`-f`&&!n.includes(`--force-with-lease`))}function Ze(e){if(!(e.includes(`reset`)&&e.includes(`--hard`)))return!1;if(e.some(e=>e===`main`||e===`master`))return!0;let t=e.filter(e=>e!==`git`&&e!==`reset`&&!e.startsWith(`-`));return t.length===0||t.includes(`HEAD`)}const Qe=[{name:`rm-recursive`,match:/^rm$/,action:`deny`,when:e=>qe(e),reason:\"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"},{name:`sudo`,match:/^sudo$/,action:`deny`,reason:`Don't use sudo. If you need elevated permissions, ask me to do it manually.`},{name:`git-force-push`,match:/^git$/,action:`deny`,when:e=>Xe(e),reason:\"Use `git push --force-with-lease` instead of `--force`.\"},{name:`git-reset-hard`,match:/^git$/,action:`deny`,when:e=>Ze(e),reason:\"Don't `reset --hard` on main. Create a branch first.\"},{name:`git-write`,match:/^git$/,action:`deny`,when:e=>Re(e),reason:`Git write operations (push, add, commit, reset, merge, etc.) are not allowed. Ask the user to run these manually.`},{name:`chmod-777`,match:/^chmod$/,action:`deny`,when:e=>Je(e),reason:`Don't use 777 permissions. Use more restrictive permissions.`},{name:`env-file-cat`,match:/^cat$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-head`,match:/^head$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-tail`,match:/^tail$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-less`,match:/^less$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-more`,match:/^more$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`env-file-grep`,match:/^grep$/,action:`deny`,when:e=>G(e),reason:`Don't read .env files. These contain secrets that should not appear in the conversation.`},{name:`npx-redirect`,match:/^npx$/,action:`deny`,reason:\"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"}],K=[{name:`vitest`,match:/^vitest$/,action:`allow`},{name:`jest`,match:/^jest$/,action:`allow`},{name:`tsc`,match:/^tsc$/,action:`allow`},{name:`tsdown`,match:/^tsdown$/,action:`allow`},{name:`oxlint`,match:/^oxlint$/,action:`allow`},{name:`oxfmt`,match:/^oxfmt$/,action:`allow`},{name:`eslint`,match:/^eslint$/,action:`allow`},{name:`prettier`,match:/^prettier$/,action:`allow`},{name:`biome`,match:/^biome$/,action:`allow`},{name:`knip`,match:/^knip$/,action:`allow`},{name:`cat`,match:/^cat$/,action:`allow`},{name:`head`,match:/^head$/,action:`allow`},{name:`tail`,match:/^tail$/,action:`allow`},{name:`grep`,match:/^grep$/,action:`allow`},{name:`ls`,match:/^ls$/,action:`allow`},{name:`wc`,match:/^wc$/,action:`allow`},{name:`uniq`,match:/^uniq$/,action:`allow`},{name:`diff`,match:/^diff$/,action:`allow`},{name:`echo`,match:/^echo$/,action:`allow`},{name:`pwd`,match:/^pwd$/,action:`allow`},{name:`which`,match:/^which$/,action:`allow`},{name:`dirname`,match:/^dirname$/,action:`allow`},{name:`basename`,match:/^basename$/,action:`allow`},{name:`realpath`,match:/^realpath$/,action:`allow`},{name:`npm`,match:/^npm$/,action:`allow`,when:e=>He(e)},{name:`test`,match:/^test$/,action:`allow`},{name:`bracket-test`,match:/^\\[$/,action:`allow`},{name:`cd`,match:/^cd$/,action:`allow`},{name:`true`,match:/^true$/,action:`allow`},{name:`colon`,match:/^:$/,action:`allow`},{name:`find`,match:/^find$/,action:`allow`,when:e=>Ge(e)},{name:`sort`,match:/^sort$/,action:`allow`,when:e=>Ke(e)},{name:`git`,match:/^git$/,action:`allow`,when:e=>Le(e)},{name:`pnpm`,match:/^pnpm$/,action:`allow`,when:e=>Be(e)},{name:`cp`,match:/^cp$/,action:`allow`,when:(e,t)=>W(e,t)},{name:`mkdir`,match:/^mkdir$/,action:`allow`,when:(e,t)=>W(e,t)},{name:`touch`,match:/^touch$/,action:`allow`,when:(e,t)=>W(e,t)}],$e=L({scripts:Se(I(),ye())});function q(e,t){let n=Y(t);if(!n)return null;let r=n[e];if(typeof r!=`string`)return null;let i=n[`pre${e}`],a=n[`post${e}`];return{pre:typeof i==`string`?i:null,main:r,post:typeof a==`string`?a:null}}function et(e,t,n){let r=nt(t,n);if(!r)return null;let i=q(e,r);return i?{...i,pkgDir:r}:null}function tt(e,t){let n=J(t);if(!n)return null;let r=[];for(let t of n){let n=q(e,t);n&&r.push({...n,pkgDir:t})}return r}function J(e){let t=rt(e);if(!t)return null;let i=[];for(let s of t){if(!s.endsWith(`/*`))continue;let t=o(e,s.slice(0,-2)),c;try{c=n(t)}catch{continue}for(let e of c){let n=a(t,e);try{if(!r(n).isDirectory())continue}catch{continue}i.push(n)}}return i.length>0?i:null}function nt(e,t){let n=J(t);if(!n)return null;for(let t of n){let n=at(t);if(n===e||n===`@${e}`||t.split(`/`).pop()===e||n&&(n.startsWith(`@`)?n.split(`/`)[1]:n)===e)return t}return null}function rt(e){try{let n=t(a(e,`pnpm-workspace.yaml`),`utf-8`).split(`\n`),r=n.findIndex(e=>/^packages\\s*:/.test(e));if(r===-1)return null;let i=[];for(let e=r+1;e<n.length;e++){let t=n[e],r=/^\\s+-\\s+(.+)$/.exec(t);if(!r)break;i.push(r[1].trim().replace(/['\"`]/g,``))}return i.length>0?i:null}catch{return null}}const it=L({name:I()});function at(e){try{let n=t(a(e,`package.json`),`utf-8`),r=it.safeParse(JSON.parse(n));return r.success?r.data.name:null}catch{return null}}function Y(e){try{let n=t(a(e,`package.json`),`utf-8`),r=$e.safeParse(JSON.parse(n));return r.success?r.data.scripts:null}catch{return null}}const ot=new Set([`LD_PRELOAD`,`LD_LIBRARY_PATH`,`DYLD_INSERT_LIBRARIES`,`NODE_OPTIONS`,`PYTHONPATH`,`PYTHONSTARTUP`,`PERL5OPT`,`PERL5LIB`,`RUBYOPT`,`RUBYLIB`,`BASH_ENV`,`ENV`,`npm_config_prefix`,`npm_config_registry`,`EDITOR`,`VISUAL`]),st=[`GIT_CONFIG_`,`GIT_PAGER`,`GIT_SSH_COMMAND`,`GIT_EDITOR`,`GIT_EXEC_PATH`];function X(e){return ot.has(e)?!0:st.some(t=>e.startsWith(t))}function ct(e,t){let n=z(e);if(!n.ok||n.tokens.length!==2)return null;let r=n.tokens;if(r[0]!==`cd`)return null;let a=r[1];return i.isAbsolute(a)?a:i.resolve(t.cwd,a)}function Z(e,t){if(t.depth>=5)return{action:`passthrough`,rule:null,reason:`max evaluation depth exceeded`};let n=e.trim();if(!n)return{action:`passthrough`,rule:null,reason:null};if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=je(n);if(r.length===0)return{action:`passthrough`,rule:null,reason:null};let i=null,a=null,o=t;for(let e of r){if(e.operator===`&&`){let t=ct(e.command,o);t&&(o={...o,cwd:t})}let t=lt(e.command,o);if(t.action===`deny`)return t;t.action===`passthrough`&&(i||=t),t.action===`allow`&&(a=t)}return i||(r.length===1&&a?a:{action:`allow`,rule:`all-segments`,reason:null})}function lt(e,t){let n=e.trim();if(n.startsWith(`(`)&&n.endsWith(`)`))return{action:`passthrough`,rule:null,reason:`subshell`};let r=z(e);if(!r.ok)return{action:`passthrough`,rule:null,reason:r.error};let a=r.tokens;if(a.length===0)return{action:`passthrough`,rule:null,reason:null};let o=0;for(;o<a.length&&/^\\w+=/.test(a[o]);){let e=a[o].split(`=`)[0];if(X(e))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${e}`};o++}if(o>=a.length)return{action:`allow`,rule:`env-only`,reason:null};a=a.slice(o);let s=Me(a,e);if(!s.safe)return{action:`passthrough`,rule:null,reason:s.reason};for(let e=0;e<a.length;e++){let t=a[e];if(!(t===`2>&1`||t===`2>/dev/null`||t===`>/dev/null`||t===`1>/dev/null`)){if(t===`>`||t===`>>`){if(a[e+1]===`/dev/null`)continue;return{action:`passthrough`,rule:null,reason:`output redirect to file`}}if(t===`<`)return{action:`passthrough`,rule:null,reason:`input redirect`}}}let c=i.basename(a[0]);if(c===`command`&&a.length>1)return Z(a.slice(1).join(` `),{...t,depth:t.depth+1});if(c===`env`){let e=1;for(;e<a.length&&/^\\w+=/.test(a[e]);){let t=a[e].split(`=`)[0];if(X(t))return{action:`passthrough`,rule:null,reason:`dangerous env var: ${t}`};e++}return e>=a.length?{action:`allow`,rule:`env-only`,reason:null}:Z(a.slice(e).join(` `),{...t,depth:t.depth+1})}if(c===`pnpm`)return ut(a,t);if(c===`npm`)return pt(a);if(c===`bash`||c===`sh`||c===`zsh`)return gt(a,t);if(c===`node`&&(a.includes(`-e`)||a.includes(`-p`)||a.includes(`--eval`)||a.includes(`--print`)))return{action:`passthrough`,rule:null,reason:`node eval`};if(c===`mv`)return{action:`passthrough`,rule:null,reason:`mv`};for(let e of Qe)if(e.match.test(c)&&(!e.when||e.when(a,t)))return{action:`deny`,rule:e.name,reason:e.reason??null};for(let e of K)if(e.match.test(c)&&(!e.when||e.when(a,t)))return{action:`allow`,rule:e.name,reason:null};return{action:`passthrough`,rule:null,reason:`no rule matched: ${c}`}}function ut(e,t){let n=e.slice(1),r,i=-1,a=!1;for(let o=0;o<n.length;o++){let s=n[o];if(s===`--filter`||s.startsWith(`--filter=`))return mt(e,n,o,t);if(s===`-r`||s===`--recursive`){a=!0;continue}if(!(s.startsWith(`-`)&&!U.has(s))){r=s,i=o;break}}if(!r)return{action:`passthrough`,rule:null,reason:null};if(r===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(r===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(r===`exec`){let e=n.slice(i+1);return e.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:Z(e.join(` `),{...t,depth:t.depth+1})}let o;if(r===`run`){let e=n[i+1];if(!e||e.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm run without script name`};o=e}else if(!U.has(r))o=r;else{for(let n of K)if(n.match.test(`pnpm`)&&(!n.when||n.when(e,t)))return{action:`allow`,rule:n.name,reason:null};return{action:`passthrough`,rule:null,reason:null}}return a?ft(o,t):dt(o,t)}function dt(e,t){let n=q(e,t.cwd);if(!n)return{action:`passthrough`,rule:null,reason:`script resolution failed`};let r={...t,depth:t.depth+1};if(n.pre){let e=Z(n.pre,r);if(e.action!==`allow`)return e}let i=Z(n.main,r);if(i.action!==`allow`)return i;if(n.post){let e=Z(n.post,r);if(e.action!==`allow`)return e}return{action:`allow`,rule:`pnpm-script:${e}`,reason:null}}function ft(e,t){let n=tt(e,t.cwd);if(!n||n.length===0)return{action:`passthrough`,rule:null,reason:`pnpm recursive`};for(let e of n){let n={...t,depth:t.depth+1,cwd:e.pkgDir??t.cwd};if(e.pre){let t=Z(e.pre,n);if(t.action!==`allow`)return t}let r=Z(e.main,n);if(r.action!==`allow`)return r;if(e.post){let t=Z(e.post,n);if(t.action!==`allow`)return t}}return{action:`allow`,rule:`pnpm-recursive:${e}`,reason:null}}function pt(e){for(let t of K)if(t.match.test(`npm`)&&(!t.when||t.when(e,{cwd:``,depth:0})))return{action:`allow`,rule:t.name,reason:null};return{action:`deny`,rule:`npm-redirect`,reason:Ue(e)}}function mt(e,t,n,r){let i=t[n],a,o;if(i.startsWith(`--filter=`))a=i.slice(9),o=n+1;else{if(a=t[n+1],!a||a.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};o=n+2}if(!a)return{action:`passthrough`,rule:null,reason:`pnpm --filter without package`};let s;for(let e=o;e<t.length;e++){let n=t[e];if(!(n.startsWith(`-`)&&!U.has(n))){s=n;break}}if(!s)return{action:`passthrough`,rule:null,reason:`pnpm --filter without subcommand`};if(s===`install`||s===`i`)return t.slice(t.indexOf(s)+1).every(e=>e.startsWith(`-`))?{action:`allow`,rule:`pnpm`,reason:null}:{action:`passthrough`,rule:null,reason:`pnpm --filter install with package arg`};if(s===`dlx`)return{action:`passthrough`,rule:null,reason:`pnpm dlx`};if(s===`add`)return{action:`passthrough`,rule:null,reason:`pnpm add`};if(new Set([`list`,`ls`,`ll`,`la`,`why`,`view`,`info`,`show`,`outdated`,`licenses`,`audit`]).has(s))return{action:`allow`,rule:`pnpm`,reason:null};if(s===`exec`){let e=t.indexOf(`exec`,o),n=t.slice(e+1);return n.length===0?{action:`passthrough`,rule:null,reason:`bare pnpm exec`}:Z(n.join(` `),{...r,depth:r.depth+1})}let c=s===`run`?t[t.indexOf(`run`,o)+1]:s;if(!c||c.startsWith(`-`))return{action:`passthrough`,rule:null,reason:`pnpm --filter run without script name`};if(U.has(c)&&c!==`run`)return{action:`passthrough`,rule:null,reason:null};let l=et(c,a,r.cwd);if(!l)return{action:`passthrough`,rule:null,reason:`filtered script resolution failed`};let u={...r,depth:r.depth+1,cwd:l.pkgDir??r.cwd};if(l.pre){let e=Z(l.pre,u);if(e.action!==`allow`)return e}let d=Z(l.main,u);if(d.action!==`allow`)return d;if(l.post){let e=Z(l.post,u);if(e.action!==`allow`)return e}return{action:`allow`,rule:`pnpm-filter:${a}:${c}`,reason:null}}const ht=new Set([`-i`,`--interactive`,`-l`,`--login`,`--init-file`,`--rcfile`,`--norc`,`--noprofile`,`-s`,`--stdin`]);function gt(e,t){let n=e.indexOf(`-c`);if(n===-1)return{action:`passthrough`,rule:null,reason:`shell without -c`};for(let t=1;t<e.length;t++){if(t===n||t===n+1)continue;let r=e[t];if(r.startsWith(`-`)||ht.has(r))return{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}}let r=e[n+1];return r?e.length>n+2?{action:`passthrough`,rule:null,reason:`shell -c with positional args`}:n===1?Z(r,{...t,depth:t.depth+1}):{action:`passthrough`,rule:null,reason:`shell -c with extra flags`}:{action:`passthrough`,rule:null,reason:`shell -c without command`}}const Q=await Ee();(Q.hook_event_name!==`PreToolUse`||Q.tool_name!==`Bash`)&&process.exit(0);const $=Z(Q.tool_input.command,{cwd:Q.cwd,depth:0});Ae({sessionId:Q.session_id,cwd:Q.cwd,command:Q.tool_input.command,decision:$.action,rule:$.rule,reason:$.reason}),$.action===`allow`?R(`allow`):$.action===`deny`&&R(`deny`,$.reason);export{};";
2394
+ const content = "/* bst-hook not built — run pnpm build from packages/claude-hooks first */";
2410
2395
  const existing = ctx.read(HOOK_PATH);
2411
2396
  if (existing === content) return {
2412
2397
  filePath: HOOK_PATH,
@@ -2467,15 +2452,10 @@ function buildSettings(ctx) {
2467
2452
  return {
2468
2453
  permissions: {
2469
2454
  allow,
2470
- deny: [
2471
- "Read(.env)",
2472
- "Read(.env.*)",
2473
- "Read(*.env)",
2474
- "Read(*.env.*)"
2475
- ]
2455
+ deny: []
2476
2456
  },
2477
2457
  hooks: { PreToolUse: [{
2478
- matcher: "Bash",
2458
+ matcher: "Bash|Read|Write|Edit|Glob|Grep",
2479
2459
  hooks: [{
2480
2460
  type: "command",
2481
2461
  command: `node "$CLAUDE_PROJECT_DIR/.claude/hooks/bst-hook.mjs"`
@@ -2492,9 +2472,14 @@ function buildSettings(ctx) {
2492
2472
  };
2493
2473
  }
2494
2474
  const BST_HOOK_MARKER = "bst-hook.mjs";
2495
- /** Check if a hook entry list already contains the bst-hook command. */
2496
- function hasBstHook(matchers) {
2497
- return matchers.some((m) => m.matcher === "Bash" && m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER)));
2475
+ /** Remove old per-tool bst-hook entries that are now covered by a consolidated matcher. */
2476
+ function removeObsoleteBstHookEntries(matchers, consolidatedMatcher) {
2477
+ const consolidatedTools = new Set(consolidatedMatcher.split("|"));
2478
+ return matchers.filter((m) => {
2479
+ if (!consolidatedTools.has(m.matcher)) return true;
2480
+ if (!m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER))) return true;
2481
+ return false;
2482
+ });
2498
2483
  }
2499
2484
  /** Remove enabledPlugins/extraKnownMarketplaces/hooks when empty to keep the file clean. */
2500
2485
  function serializeSettings$1(settings) {
@@ -2530,8 +2515,15 @@ function writeOrMergeSettings(ctx, filePath, generated) {
2530
2515
  const mergedHooks = { ...settings.hooks };
2531
2516
  for (const [event, generatedMatchers] of Object.entries(generated.hooks)) {
2532
2517
  const existingMatchers = mergedHooks[event] ?? [];
2533
- if (!hasBstHook(existingMatchers)) {
2534
- mergedHooks[event] = [...existingMatchers, ...generatedMatchers];
2518
+ for (const gm of generatedMatchers) {
2519
+ if (existingMatchers.some((m) => m.matcher === gm.matcher && m.hooks.some((h) => h.command.includes(BST_HOOK_MARKER)))) continue;
2520
+ if (gm.matcher.includes("|")) {
2521
+ const before = (mergedHooks[event] ?? []).length;
2522
+ mergedHooks[event] = removeObsoleteBstHookEntries(mergedHooks[event] ?? [], gm.matcher);
2523
+ const removed = before - (mergedHooks[event] ?? []).length;
2524
+ if (removed > 0) hookChanges += removed;
2525
+ }
2526
+ mergedHooks[event] = [...mergedHooks[event] ?? [], gm];
2535
2527
  hookChanges++;
2536
2528
  }
2537
2529
  }
@@ -3205,7 +3197,7 @@ async function runGenerators(ctx) {
3205
3197
  function generateMigratePrompt(results, config, detected) {
3206
3198
  const sections = [];
3207
3199
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3208
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.40.1 repo:sync\` on ${timestamp}_\n`);
3200
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.40.3 repo:sync\` on ${timestamp}_\n`);
3209
3201
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
3210
3202
  if (changed.length > 0) {
3211
3203
  const items = changed.map((r) => {
@@ -5217,6 +5209,16 @@ const dockerCheckCommand = defineCommand({
5217
5209
  });
5218
5210
  //#endregion
5219
5211
  //#region src/commands/hooks-audit.ts
5212
+ /** Walk up from `cwd` to find the nearest directory containing `.claude/`. */
5213
+ function findProjectRoot(cwd) {
5214
+ let dir = path.resolve(cwd);
5215
+ while (true) {
5216
+ if (existsSync(path.join(dir, ".claude"))) return dir;
5217
+ const parent = path.dirname(dir);
5218
+ if (parent === dir) return cwd;
5219
+ dir = parent;
5220
+ }
5221
+ }
5220
5222
  const AuditEntrySchema = z.object({
5221
5223
  timestamp: z.string(),
5222
5224
  sessionId: z.string(),
@@ -5290,7 +5292,7 @@ const hooksAuditCommand = defineCommand({
5290
5292
  }
5291
5293
  },
5292
5294
  async run({ args }) {
5293
- const targetDir = path.resolve(args.dir ?? ".");
5295
+ const targetDir = args.dir ? path.resolve(args.dir) : findProjectRoot(process.cwd());
5294
5296
  let entries = parseAuditLog(path.join(targetDir, ".claude/hooks/bst-hook.audit.local.jsonl"));
5295
5297
  if (entries.length === 0) {
5296
5298
  write("No audit log entries found.");
@@ -5331,7 +5333,7 @@ const hooksAuditCommand = defineCommand({
5331
5333
  const main = defineCommand({
5332
5334
  meta: {
5333
5335
  name: "bst",
5334
- version: "0.40.1",
5336
+ version: "0.40.3",
5335
5337
  description: "Bootstrap and maintain standardized TypeScript project tooling"
5336
5338
  },
5337
5339
  subCommands: {
@@ -5348,7 +5350,7 @@ const main = defineCommand({
5348
5350
  "hooks:audit": hooksAuditCommand
5349
5351
  }
5350
5352
  });
5351
- console.log(`@bensandee/tooling v0.40.1`);
5353
+ console.log(`@bensandee/tooling v0.40.3`);
5352
5354
  async function run() {
5353
5355
  await runMain(main);
5354
5356
  process.exit(process.exitCode ?? 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.40.1",
3
+ "version": "0.40.3",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -51,22 +51,15 @@
51
51
  "@types/node": "24.12.0",
52
52
  "@types/picomatch": "4.0.2",
53
53
  "commit-and-tag-version": "12.7.1",
54
- "knip": "6.0.3",
55
- "lefthook": "2.1.4",
56
- "oxfmt": "0.41.0",
57
- "oxlint": "1.56.0",
58
54
  "prettier": "3.8.1",
59
55
  "release-it": "19.2.4",
60
56
  "tsdown": "0.21.4",
61
- "typescript": "5.9.3",
62
- "vitest": "4.1.0",
63
- "@bensandee/claude-hooks": "0.2.0",
57
+ "@bensandee/claude-hooks": "0.3.0",
64
58
  "@bensandee/config": "0.10.0"
65
59
  },
66
60
  "scripts": {
67
61
  "build": "tsdown",
68
62
  "dev": "tsdown --watch",
69
- "typecheck": "tsc --noEmit",
70
63
  "test": "vitest run",
71
64
  "generate:schema": "node scripts/generate-schema.ts"
72
65
  }