@massu/core 2.5.5 → 2.5.6

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.
@@ -4453,8 +4453,7 @@ function isDirectInvocation(moduleUrl) {
4453
4453
  }
4454
4454
  }
4455
4455
 
4456
- // src/hooks/fix-detector.ts
4457
- var HOOK_EVENT = "PostToolUse";
4456
+ // src/hooks/lib/test-or-fixture-path.ts
4458
4457
  var TEST_OR_FIXTURE_PATH_PATTERNS = [
4459
4458
  /(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
4460
4459
  /\.(test|spec)\.[cm]?[jt]sx?$/,
@@ -4463,6 +4462,9 @@ var TEST_OR_FIXTURE_PATH_PATTERNS = [
4463
4462
  function isTestOrFixturePath(filePath) {
4464
4463
  return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
4465
4464
  }
4465
+
4466
+ // src/hooks/fix-detector.ts
4467
+ var HOOK_EVENT = "PostToolUse";
4466
4468
  var FIX_HEURISTICS = [
4467
4469
  {
4468
4470
  name: "removed_broken_code",
@@ -4054,7 +4054,244 @@ var init_memory_db = __esm({
4054
4054
  }
4055
4055
  });
4056
4056
 
4057
- // src/hooks/pre-delete-check.ts
4057
+ // src/hooks/lib/security-gate-checks.ts
4058
+ var DANGEROUS_BASH_PATTERNS = [
4059
+ // BLOCK — destructive and irreversible, or arbitrary remote code execution.
4060
+ { pattern: /rm\s+-[a-z]*r[a-z]*f[a-z]*\s+\/(?:\s|$)/, label: "rm -rf /", severity: "block" },
4061
+ { pattern: /rm\s+-[a-z]*f[a-z]*r[a-z]*\s+\/(?:\s|$)/, label: "rm -rf /", severity: "block" },
4062
+ { pattern: /curl\s+.*\|\s*(?:bash|sh|zsh)/, label: "curl | bash (remote code execution)", severity: "block" },
4063
+ { pattern: /wget\s+.*\|\s*(?:bash|sh|zsh)/, label: "wget | bash (remote code execution)", severity: "block" },
4064
+ { pattern: />\s*\/etc\/passwd/, label: "write to /etc/passwd", severity: "block" },
4065
+ { pattern: />\s*\/etc\/shadow/, label: "write to /etc/shadow", severity: "block" },
4066
+ { pattern: />\s*\/etc\/sudoers/, label: "write to /etc/sudoers", severity: "block" },
4067
+ { pattern: /dd\s+if=.*of=\/dev\/(?:sda|sdb|hda|hdb|nvme)/, label: "dd to raw device", severity: "block" },
4068
+ { pattern: /mkfs\s+\/dev\//, label: "format disk device", severity: "block" },
4069
+ { pattern: /:\(\)\s*\{\s*:\|:\s*&\s*\}/, label: "fork bomb", severity: "block" },
4070
+ { pattern: /eval\s+.*\$\(.*curl/, label: "eval with remote curl", severity: "block" },
4071
+ { pattern: /base64\s+-d\s+.*\|\s*(?:bash|sh|zsh)/, label: "base64 decoded shell exec", severity: "block" },
4072
+ // WARN — bad practice, but has legitimate uses (fixing a broken permission bit,
4073
+ // a throwaway container). Blocking these would make the product hostile.
4074
+ { pattern: /chmod\s+777/, label: "chmod 777 (world-writable permissions)", severity: "warn" },
4075
+ { pattern: /chmod\s+-R\s+777/, label: "chmod -R 777 (world-writable permissions)", severity: "warn" }
4076
+ ];
4077
+ var PROTECTED_FILE_PATTERNS = [
4078
+ { pattern: /\.env$/, label: ".env file" },
4079
+ { pattern: /\.env\./, label: ".env.* file" },
4080
+ { pattern: /credentials(?:\.json)?$/, label: "credentials file" },
4081
+ { pattern: /\.pem$/, label: ".pem certificate/key file" },
4082
+ { pattern: /\.key$/, label: ".key file" },
4083
+ { pattern: /\.p12$/, label: ".p12 keystore file" },
4084
+ { pattern: /\.pfx$/, label: ".pfx keystore file" },
4085
+ { pattern: /id_rsa$/, label: "RSA private key" },
4086
+ { pattern: /id_ed25519$/, label: "Ed25519 private key" },
4087
+ { pattern: /id_ecdsa$/, label: "ECDSA private key" },
4088
+ { pattern: /\.ssh\/config$/, label: "SSH config file" },
4089
+ { pattern: /secrets\.yaml$/, label: "secrets.yaml file" },
4090
+ { pattern: /secrets\.yml$/, label: "secrets.yml file" },
4091
+ { pattern: /\.netrc$/, label: ".netrc credentials file" },
4092
+ { pattern: /aws\/credentials$/, label: "AWS credentials file" },
4093
+ { pattern: /kubeconfig$/, label: "Kubernetes config file" }
4094
+ ];
4095
+ var SECRET_BASENAME_EXTENSIONS = ["pem", "key", "p12", "pfx", "secret"];
4096
+ var DOTENV_TEMPLATE_SUFFIXES = ["example", "template", "sample", "defaults", "dist"];
4097
+ var SECRET_EXACT_BASENAMES = [
4098
+ "id_rsa",
4099
+ "id_ed25519",
4100
+ "id_ecdsa",
4101
+ ".netrc",
4102
+ "kubeconfig",
4103
+ "credentials.json",
4104
+ "secrets.yaml",
4105
+ "secrets.yml"
4106
+ ];
4107
+ var CREDENTIALS_PATH_SUFFIX = "/credentials";
4108
+ var DOTENV = ".env";
4109
+ var SECRET_DATA_EXTENSIONS = ["yaml", "yml", "json", "toml", "ini", "env", "conf", "properties", "txt"];
4110
+ function looksLikeCode(token) {
4111
+ return /[()[\]{};=`$<>|&*?!"']/.test(token) || /\s/.test(token);
4112
+ }
4113
+ function namesSecretFile(token) {
4114
+ if (!token || looksLikeCode(token)) return null;
4115
+ const clean = token.replace(/[,:.]+$/, "");
4116
+ if (!clean) return null;
4117
+ const base = clean.split("/").pop() ?? "";
4118
+ if (!base) return null;
4119
+ if (SECRET_EXACT_BASENAMES.includes(base)) return base;
4120
+ if (clean.endsWith(".ssh/config")) return "SSH config file";
4121
+ if (clean.endsWith(CREDENTIALS_PATH_SUFFIX)) return "credentials file";
4122
+ if (base === DOTENV) return `${DOTENV} file`;
4123
+ if (base.startsWith(`${DOTENV}.`)) {
4124
+ const suffix = base.slice(DOTENV.length + 1);
4125
+ return DOTENV_TEMPLATE_SUFFIXES.includes(suffix) ? null : `${base} file`;
4126
+ }
4127
+ if (base.endsWith(DOTENV) && clean.includes("/")) return `${base} file`;
4128
+ for (const ext2 of SECRET_BASENAME_EXTENSIONS) {
4129
+ if (base.endsWith(`.${ext2}`)) return `${base} file`;
4130
+ }
4131
+ const parts = base.toLowerCase().split(".");
4132
+ const ext = parts.length > 1 ? parts[parts.length - 1] : "";
4133
+ if (SECRET_DATA_EXTENSIONS.includes(ext) && parts.slice(0, -1).some((p) => p === "secret" || p === "secrets")) {
4134
+ return `${base} file`;
4135
+ }
4136
+ return null;
4137
+ }
4138
+ function shellTokens(command) {
4139
+ const out = [];
4140
+ let cur = "";
4141
+ let quote = null;
4142
+ for (const ch of command) {
4143
+ if (quote) {
4144
+ if (ch === quote) quote = null;
4145
+ else cur += ch;
4146
+ continue;
4147
+ }
4148
+ if (ch === "'" || ch === '"') {
4149
+ quote = ch;
4150
+ cur += ch;
4151
+ continue;
4152
+ }
4153
+ if (/\s/.test(ch)) {
4154
+ if (cur) out.push(cur);
4155
+ cur = "";
4156
+ continue;
4157
+ }
4158
+ cur += ch;
4159
+ }
4160
+ if (cur) out.push(cur);
4161
+ return out;
4162
+ }
4163
+ function secretFilesInCommand(command) {
4164
+ const hits = [];
4165
+ for (const token of shellTokens(command)) {
4166
+ const label = namesSecretFile(token);
4167
+ if (label) hits.push(label);
4168
+ }
4169
+ return hits;
4170
+ }
4171
+ function checkBashCommand(command) {
4172
+ for (const { pattern, label, severity } of DANGEROUS_BASH_PATTERNS) {
4173
+ if (pattern.test(command)) {
4174
+ return { label, severity };
4175
+ }
4176
+ }
4177
+ return null;
4178
+ }
4179
+ function checkFilePath(filePath) {
4180
+ for (const { pattern, label } of PROTECTED_FILE_PATTERNS) {
4181
+ if (pattern.test(filePath)) {
4182
+ return label;
4183
+ }
4184
+ }
4185
+ return null;
4186
+ }
4187
+ var DANGEROUS_PYTHON_PATTERNS = [
4188
+ { pattern: /\beval\s*\(/, label: "Python eval() \u2014 arbitrary code execution", scope: "code" },
4189
+ { pattern: /\bexec\s*\(/, label: "Python exec() \u2014 arbitrary code execution", scope: "code" },
4190
+ { pattern: /\b__import__\s*\(/, label: "Python __import__() \u2014 dynamic import (potential code injection)", scope: "code" },
4191
+ { pattern: /subprocess\.call\([^)]*shell\s*=\s*True/, label: "subprocess.call(shell=True) \u2014 shell injection risk", scope: "code" },
4192
+ { pattern: /subprocess\.Popen\([^)]*shell\s*=\s*True/, label: "subprocess.Popen(shell=True) \u2014 shell injection risk", scope: "code" },
4193
+ { pattern: /os\.system\s*\(/, label: "os.system() \u2014 shell injection risk", scope: "code" },
4194
+ { pattern: /\bf['"].*\{.*\}.*['"].*(?:execute|cursor|query)/, label: "f-string in SQL \u2014 SQL injection risk", scope: "raw" },
4195
+ { pattern: /['"].*%s.*['"].*%.*(?:execute|cursor|query)/, label: "String formatting in SQL \u2014 SQL injection risk", scope: "raw" }
4196
+ ];
4197
+ function stripPythonCommentsAndStringBodies(src) {
4198
+ let out = "";
4199
+ let i = 0;
4200
+ while (i < src.length) {
4201
+ const ch = src[i];
4202
+ if (ch === "#") {
4203
+ while (i < src.length && src[i] !== "\n") i++;
4204
+ continue;
4205
+ }
4206
+ if (ch === '"' || ch === "'") {
4207
+ const triple = src.startsWith(ch.repeat(3), i);
4208
+ const delim = triple ? ch.repeat(3) : ch;
4209
+ out += delim;
4210
+ i += delim.length;
4211
+ let closed = false;
4212
+ while (i < src.length) {
4213
+ if (src[i] === "\\") {
4214
+ i += 2;
4215
+ continue;
4216
+ }
4217
+ if (src.startsWith(delim, i)) {
4218
+ out += delim;
4219
+ i += delim.length;
4220
+ closed = true;
4221
+ break;
4222
+ }
4223
+ if (!triple && src[i] === "\n") break;
4224
+ i++;
4225
+ }
4226
+ if (!closed) return null;
4227
+ continue;
4228
+ }
4229
+ out += ch;
4230
+ i++;
4231
+ }
4232
+ return out;
4233
+ }
4234
+ function checkPythonContent(content) {
4235
+ const stripped = stripPythonCommentsAndStringBodies(content);
4236
+ for (const { pattern, label, scope } of DANGEROUS_PYTHON_PATTERNS) {
4237
+ const subject = scope === "raw" || stripped === null ? content : stripped;
4238
+ if (pattern.test(subject)) {
4239
+ return label;
4240
+ }
4241
+ }
4242
+ return null;
4243
+ }
4244
+ function runSecurityGateFindings(hookInput) {
4245
+ const findings = [];
4246
+ const { tool_name, tool_input } = hookInput;
4247
+ if (tool_name === "Bash" && tool_input.command) {
4248
+ const secretFiles = secretFilesInCommand(tool_input.command);
4249
+ if (secretFiles.length > 0) {
4250
+ findings.push({
4251
+ severity: "block",
4252
+ message: `SECURITY GATE: command names a secret file: ${secretFiles.join(", ")}
4253
+ Command: ${tool_input.command.slice(0, 200)}
4254
+ BLOCKED. If this file is a template or fixture, name it with a template suffix (.example/.template/.sample) rather than weakening this check.`
4255
+ });
4256
+ }
4257
+ const violation = checkBashCommand(tool_input.command);
4258
+ if (violation) {
4259
+ findings.push({
4260
+ severity: violation.severity,
4261
+ message: `SECURITY GATE: Dangerous command pattern detected: ${violation.label}
4262
+ Command: ${tool_input.command.slice(0, 200)}` + (violation.severity === "block" ? `
4263
+ BLOCKED. This pattern has no legitimate use in an agent session.` : `
4264
+ Review carefully before proceeding.`)
4265
+ });
4266
+ }
4267
+ }
4268
+ if ((tool_name === "Write" || tool_name === "Edit") && tool_input.file_path) {
4269
+ const violation = checkFilePath(tool_input.file_path);
4270
+ if (violation) {
4271
+ findings.push({
4272
+ severity: "warn",
4273
+ message: `SECURITY GATE: Attempt to write to protected file: ${violation}
4274
+ Path: ${tool_input.file_path}
4275
+ Ensure this is intentional and no secrets will be exposed.`
4276
+ });
4277
+ }
4278
+ }
4279
+ const pyContent = tool_input.content || tool_input.new_string;
4280
+ if ((tool_name === "Write" || tool_name === "Edit") && tool_input.file_path?.endsWith(".py") && pyContent) {
4281
+ const pyViolation = checkPythonContent(pyContent);
4282
+ if (pyViolation) {
4283
+ findings.push({
4284
+ severity: "warn",
4285
+ message: `SECURITY GATE: Dangerous Python pattern detected: ${pyViolation}
4286
+ File: ${tool_input.file_path}
4287
+ Review carefully before proceeding.`
4288
+ });
4289
+ }
4290
+ }
4291
+ return findings;
4292
+ }
4293
+
4294
+ // src/hooks/lib/pre-delete-checks.ts
4058
4295
  init_db_driver();
4059
4296
  import { existsSync as existsSync9 } from "fs";
4060
4297
 
@@ -4150,7 +4387,7 @@ function getFeatureImpact(db, filePaths) {
4150
4387
  };
4151
4388
  }
4152
4389
 
4153
- // src/hooks/pre-delete-check.ts
4390
+ // src/hooks/lib/pre-delete-checks.ts
4154
4391
  init_config();
4155
4392
 
4156
4393
  // src/lib/source-layout.ts
@@ -4200,58 +4437,7 @@ function isUnderSourceDir(path, layout = getSourceLayout()) {
4200
4437
  return layout.sourceDirs.some((d) => path.startsWith(`${d}/`));
4201
4438
  }
4202
4439
 
4203
- // src/hooks/lib/write-hook-message.ts
4204
- var HOOK_EVENTS = [
4205
- "UserPromptSubmit",
4206
- "UserPromptExpansion",
4207
- "SessionStart",
4208
- "PreToolUse",
4209
- "PostToolUse",
4210
- "Stop",
4211
- "SubagentStop",
4212
- "PreCompact"
4213
- ];
4214
- var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4215
- function isHookEvent(value) {
4216
- return typeof value === "string" && HOOK_EVENTS.includes(value);
4217
- }
4218
- function writeHookContext(hookEventName, message) {
4219
- if (!isHookEvent(hookEventName)) {
4220
- process.stderr.write(
4221
- `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4222
- `
4223
- );
4224
- return;
4225
- }
4226
- if (typeof message !== "string" || message.length === 0) return;
4227
- let additionalContext = message;
4228
- if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4229
- const marker = `
4230
- [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4231
- additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4232
- }
4233
- process.stdout.write(
4234
- JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4235
- );
4236
- }
4237
-
4238
- // src/hooks/pre-delete-check.ts
4239
- init_hook_failure_signal();
4240
-
4241
- // src/hooks/lib/is-direct-invocation.ts
4242
- import { pathToFileURL } from "url";
4243
- function isDirectInvocation(moduleUrl) {
4244
- const entry = process.argv[1];
4245
- if (!entry) return false;
4246
- try {
4247
- return moduleUrl === pathToFileURL(entry).href;
4248
- } catch {
4249
- return false;
4250
- }
4251
- }
4252
-
4253
- // src/hooks/pre-delete-check.ts
4254
- var HOOK_EVENT = "PreToolUse";
4440
+ // src/hooks/lib/pre-delete-checks.ts
4255
4441
  var PROJECT_ROOT2 = getProjectRoot();
4256
4442
  var KNOWLEDGE_PROTECTED_FILES = [
4257
4443
  "knowledge-db.ts",
@@ -4390,30 +4576,154 @@ function runPreDeleteChecks(hookInput) {
4390
4576
  }
4391
4577
  return messages;
4392
4578
  }
4393
- async function main() {
4394
- try {
4395
- const input = await readStdin();
4396
- const hookInput = JSON.parse(input);
4397
- const messages = runPreDeleteChecks(hookInput);
4398
- for (const msg of messages) writeHookContext(HOOK_EVENT, msg);
4399
- } catch (err) {
4400
- recordHookFailure("pre-delete-check", err);
4401
- process.stderr.write("MASSU SECURITY GATE \u2014 BLOCKED (gate could not evaluate this call)\n");
4402
- process.exit(2);
4579
+
4580
+ // src/hooks/lib/write-hook-message.ts
4581
+ var HOOK_EVENTS = [
4582
+ "UserPromptSubmit",
4583
+ "UserPromptExpansion",
4584
+ "SessionStart",
4585
+ "PreToolUse",
4586
+ "PostToolUse",
4587
+ "Stop",
4588
+ "SubagentStop",
4589
+ "PreCompact"
4590
+ ];
4591
+ var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4592
+ function isHookEvent(value) {
4593
+ return typeof value === "string" && HOOK_EVENTS.includes(value);
4594
+ }
4595
+ function writeHookContext(hookEventName, message) {
4596
+ if (!isHookEvent(hookEventName)) {
4597
+ process.stderr.write(
4598
+ `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4599
+ `
4600
+ );
4601
+ return;
4403
4602
  }
4404
- process.exit(0);
4603
+ if (typeof message !== "string" || message.length === 0) return;
4604
+ let additionalContext = message;
4605
+ if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4606
+ const marker = `
4607
+ [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4608
+ additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4609
+ }
4610
+ process.stdout.write(
4611
+ JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4612
+ );
4405
4613
  }
4406
- function readStdin() {
4407
- return new Promise((resolve5) => {
4614
+
4615
+ // src/hooks/lib/read-stdin.ts
4616
+ var StdinTimeoutError = class extends Error {
4617
+ bytesRead;
4618
+ constructor(ms, bytesRead) {
4619
+ super(
4620
+ `stdin did not reach EOF within ${ms}ms (${bytesRead} bytes read). The payload is INCOMPLETE and must not be treated as valid input.`
4621
+ );
4622
+ this.name = "StdinTimeoutError";
4623
+ this.bytesRead = bytesRead;
4624
+ }
4625
+ };
4626
+ function readStdinToEof(deadlineMs = 4e3) {
4627
+ return new Promise((resolve5, reject) => {
4408
4628
  let data = "";
4629
+ let settled = false;
4630
+ const timer = setTimeout(() => {
4631
+ if (settled) return;
4632
+ settled = true;
4633
+ reject(new StdinTimeoutError(deadlineMs, data.length));
4634
+ }, deadlineMs);
4635
+ const finish = (fn) => {
4636
+ if (settled) return;
4637
+ settled = true;
4638
+ clearTimeout(timer);
4639
+ fn();
4640
+ };
4409
4641
  process.stdin.setEncoding("utf-8");
4410
4642
  process.stdin.on("data", (chunk) => {
4411
4643
  data += chunk;
4412
4644
  });
4413
- process.stdin.on("end", () => resolve5(data));
4414
- setTimeout(() => resolve5(data), 400);
4645
+ process.stdin.on("end", () => finish(() => resolve5(data)));
4646
+ process.stdin.on("error", (err) => finish(() => reject(err)));
4415
4647
  });
4416
4648
  }
4649
+
4650
+ // src/hooks/lib/pre-tool-use-decision.ts
4651
+ init_hook_failure_signal();
4652
+ var EXIT_DENY = 2;
4653
+ var EXIT_ALLOW = 0;
4654
+ function deny(reason, detail) {
4655
+ process.stderr.write(
4656
+ `MASSU SECURITY GATE \u2014 BLOCKED
4657
+
4658
+ ${reason}
4659
+ ` + (detail ? `
4660
+ ${detail}
4661
+ ` : "") + `
4662
+ The gate denies by default when it cannot verify a command is safe.
4663
+ `
4664
+ );
4665
+ process.exit(EXIT_DENY);
4666
+ }
4667
+ async function runPreToolUseDecision(hookName, event) {
4668
+ let hookInput;
4669
+ let input = "";
4670
+ try {
4671
+ input = await readStdinToEof();
4672
+ if (!input.trim()) {
4673
+ recordHookFailure(hookName, new Error("empty stdin payload"));
4674
+ deny("The gate received an EMPTY payload and could not determine what tool call was requested.");
4675
+ }
4676
+ hookInput = JSON.parse(input);
4677
+ } catch (err) {
4678
+ recordHookFailure(hookName, err, {
4679
+ phase: "read-parse",
4680
+ bytesRead: input.length,
4681
+ endsWithBrace: input.trimEnd().endsWith("}")
4682
+ });
4683
+ deny(
4684
+ "The gate could not READ or PARSE the tool-call payload, so it cannot know what it was being asked to approve.",
4685
+ err instanceof Error ? `Cause: ${err.message}` : void 0
4686
+ );
4687
+ }
4688
+ let findings;
4689
+ try {
4690
+ const security = runSecurityGateFindings(hookInput);
4691
+ const deletes = runPreDeleteChecks(hookInput).map(
4692
+ (message) => ({ severity: "warn", message })
4693
+ );
4694
+ findings = [...security, ...deletes];
4695
+ } catch (err) {
4696
+ recordHookFailure(hookName, err, { phase: "evaluate" });
4697
+ deny(
4698
+ "A security check CRASHED while evaluating this tool call. A check that did not run has not passed.",
4699
+ err instanceof Error ? `Cause: ${err.message}` : void 0
4700
+ );
4701
+ }
4702
+ for (const f of findings.filter((x) => x.severity === "warn")) {
4703
+ writeHookContext(event, f.message);
4704
+ }
4705
+ const blocking = findings.filter((f) => f.severity === "block");
4706
+ if (blocking.length > 0) deny(blocking.map((f) => f.message).join("\n\n"));
4707
+ process.exit(EXIT_ALLOW);
4708
+ }
4709
+
4710
+ // src/hooks/lib/is-direct-invocation.ts
4711
+ import { pathToFileURL } from "url";
4712
+ function isDirectInvocation(moduleUrl) {
4713
+ const entry = process.argv[1];
4714
+ if (!entry) return false;
4715
+ try {
4716
+ return moduleUrl === pathToFileURL(entry).href;
4717
+ } catch {
4718
+ return false;
4719
+ }
4720
+ }
4721
+
4722
+ // src/hooks/pre-delete-check.ts
4723
+ var HOOK_EVENT = "PreToolUse";
4724
+ async function main() {
4725
+ await runPreToolUseDecision("pre-delete-check", HOOK_EVENT);
4726
+ }
4417
4727
  if (isDirectInvocation(import.meta.url)) {
4418
4728
  main();
4419
4729
  }