@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.
- package/dist/bin.mjs +86 -84
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1351
|
-
"pnpm": "10.
|
|
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.
|
|
1367
|
-
"pnpm": "10.
|
|
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.
|
|
1383
|
-
"pnpm": "10.
|
|
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.
|
|
1399
|
-
"pnpm": "10.
|
|
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.
|
|
1418
|
-
"pnpm": "10.
|
|
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.
|
|
1434
|
-
"pnpm": "10.
|
|
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.
|
|
1450
|
-
"pnpm": "10.
|
|
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.
|
|
1491
|
-
"pnpm": "10.
|
|
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.
|
|
1507
|
-
"pnpm": "10.
|
|
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.
|
|
1525
|
-
"pnpm": "10.
|
|
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.
|
|
1543
|
-
"pnpm": "10.
|
|
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.
|
|
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.
|
|
1583
|
-
"pnpm": "10.
|
|
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.
|
|
1599
|
-
"pnpm": "10.
|
|
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.
|
|
1683
|
-
"pnpm": "10.
|
|
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
|
-
/**
|
|
2496
|
-
function
|
|
2497
|
-
|
|
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
|
-
|
|
2534
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
}
|