@hasna/hooks 0.10.8 → 0.10.9

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.
@@ -1303,7 +1303,7 @@ function segmentOperation(segment) {
1303
1303
  const trimmed = segment.trim();
1304
1304
  if (!trimmed)
1305
1305
  return "read";
1306
- if (/(?:^|\s)(?:rm|rmdir|unlink|shred|trash|rmtree|del)(?:\s|$)/.test(trimmed))
1306
+ if (/(?:^|\s)\\?(?:[^\s;&|<>()]*\/)?(?:rm|rmdir|unlink|shred|trash|rmtree|del)(?:\s|$)/.test(trimmed))
1307
1307
  return "delete";
1308
1308
  if (/\bgit\b/.test(trimmed)) {
1309
1309
  if (/\bgit\b[^;&|]*\b(?:clean|rm)\b/.test(trimmed))
@@ -1312,7 +1312,7 @@ function segmentOperation(segment) {
1312
1312
  return "write";
1313
1313
  return "read";
1314
1314
  }
1315
- if (/(?:^|\s)(?:mkdir|mkfile|touch|mv|cp|ln|tee|install|dd)(?:\s|$)/.test(trimmed))
1315
+ if (/(?:^|\s)\\?(?:[^\s;&|<>()]*\/)?(?:mkdir|mkfile|touch|mv|cp|ln|tee|install|dd)(?:\s|$)/.test(trimmed))
1316
1316
  return "write";
1317
1317
  if (/\b(?:curl|wget)\b/.test(trimmed)) {
1318
1318
  if (WRITE_FLAGS.test(trimmed))
@@ -1455,8 +1455,17 @@ function bashTargets(command, home, cwd) {
1455
1455
  if (foundExplicit || stage.op === "read")
1456
1456
  continue;
1457
1457
  const words = shellWords(stage.commandPart);
1458
+ let operandStart = 1;
1459
+ const tool = (words[0]?.text ?? "").split("/").pop();
1460
+ if (tool === "trash" && ["put", "rm", "delete", "remove"].includes(words[1]?.text ?? ""))
1461
+ operandStart = 2;
1462
+ if (tool === "git") {
1463
+ const sub = words.findIndex((word, index2) => index2 > 0 && (word.text === "rm" || word.text === "clean"));
1464
+ if (sub > 0)
1465
+ operandStart = sub + 1;
1466
+ }
1458
1467
  let endOfOptions = false;
1459
- const nonFlag = words.slice(1).flatMap((word) => {
1468
+ const nonFlag = words.slice(operandStart).flatMap((word) => {
1460
1469
  if (!endOfOptions && word.text === "--") {
1461
1470
  endOfOptions = true;
1462
1471
  return [];
@@ -1466,7 +1475,7 @@ function bashTargets(command, home, cwd) {
1466
1475
  endOfOptions = true;
1467
1476
  return [word.opaque ? "." : word.text];
1468
1477
  });
1469
- const everyOperand = stage.op === "delete" || /(?:^|\s)(?:mkdir|mkfile|touch|tee|truncate|install|mv)(?:\s|$)/.test(stage.commandPart) || /(?:^|\s)sed(?:\s|$)/.test(stage.commandPart) && /(?:^|\s)(?:-i\S*|--in-place\S*)(?:\s|$)/.test(stage.commandPart);
1478
+ const everyOperand = stage.op === "delete" || /(?:^|\s)\\?(?:[^\s;&|<>()]*\/)?(?:mkdir|mkfile|touch|tee|truncate|install|mv)(?:\s|$)/.test(stage.commandPart) || /(?:^|\s)sed(?:\s|$)/.test(stage.commandPart) && /(?:^|\s)(?:-i\S*|--in-place\S*)(?:\s|$)/.test(stage.commandPart);
1470
1479
  const operandTargets = [];
1471
1480
  if (everyOperand) {
1472
1481
  for (const operand of nonFlag.length > 0 ? nonFlag : ["."])
@@ -51,8 +51,11 @@ word of a later read-only stage (`| head -60`) is never taken as the write
51
51
  target of an earlier stage. Pipes inside quotes or command substitutions are
52
52
  not stage boundaries.
53
53
  A protected path named anywhere in a pipeline takes the strongest operation
54
- of any stage (`find <root>/x | xargs rm -rf` is a delete). Deletes and
55
- `mkdir`/`touch` check every operand; other writers check their last one.
54
+ of any stage (`find <root>/x | xargs rm -rf` is a delete). Deletes,
55
+ `mkdir`/`touch`, `tee`, `truncate`, `install`, `mv` and `sed -i` check every
56
+ operand (options end at `--` or at the first operand); other writers check
57
+ their last one, plus any `-t`/`--target-directory` value. A delete tool's
58
+ subcommand word is not an operand (`trash put <path>`, `git rm <path>`).
56
59
  An operand or redirect target that cannot be read statically (a variable
57
60
  other than `$HOME`, `$( )`, backticks, a glob) fails closed to the directory
58
61
  the command runs in, so `rm -rf "$TMPDIR/x"` run from a protected checkout is
@@ -365,13 +365,13 @@ function segmentOperation(segment: string): Operation {
365
365
  const trimmed = segment.trim();
366
366
  if (!trimmed) return "read";
367
367
 
368
- if (/(?:^|\s)(?:rm|rmdir|unlink|shred|trash|rmtree|del)(?:\s|$)/.test(trimmed)) return "delete";
368
+ if (/(?:^|\s)\\?(?:[^\s;&|<>()]*\/)?(?:rm|rmdir|unlink|shred|trash|rmtree|del)(?:\s|$)/.test(trimmed)) return "delete";
369
369
  if (/\bgit\b/.test(trimmed)) {
370
370
  if (/\bgit\b[^;&|]*\b(?:clean|rm)\b/.test(trimmed)) return "delete";
371
371
  if (/\bgit\b[^;&|]*\b(?:clone|init)\b/.test(trimmed)) return "write";
372
372
  return "read";
373
373
  }
374
- if (/(?:^|\s)(?:mkdir|mkfile|touch|mv|cp|ln|tee|install|dd)(?:\s|$)/.test(trimmed)) return "write";
374
+ if (/(?:^|\s)\\?(?:[^\s;&|<>()]*\/)?(?:mkdir|mkfile|touch|mv|cp|ln|tee|install|dd)(?:\s|$)/.test(trimmed)) return "write";
375
375
  if (/\b(?:curl|wget)\b/.test(trimmed)) {
376
376
  if (WRITE_FLAGS.test(trimmed)) return "write";
377
377
  return "read";
@@ -586,8 +586,17 @@ export function bashTargets(command: string, home: string, cwd: string): PathTar
586
586
  // Operands after the command word. Options end at `--` or, as BSD
587
587
  // getopt does, at the first operand: every later word is an operand
588
588
  // even when it starts with `-`.
589
+ // A delete tool's subcommand word is not an operand: `trash put <path>`
590
+ // and `git [-C dir] rm|clean ...` act on the words after it.
591
+ let operandStart = 1;
592
+ const tool = (words[0]?.text ?? "").split("/").pop();
593
+ if (tool === "trash" && ["put", "rm", "delete", "remove"].includes(words[1]?.text ?? "")) operandStart = 2;
594
+ if (tool === "git") {
595
+ const sub = words.findIndex((word, index) => index > 0 && (word.text === "rm" || word.text === "clean"));
596
+ if (sub > 0) operandStart = sub + 1;
597
+ }
589
598
  let endOfOptions = false;
590
- const nonFlag = words.slice(1).flatMap((word) => {
599
+ const nonFlag = words.slice(operandStart).flatMap((word) => {
591
600
  if (!endOfOptions && word.text === "--") {
592
601
  endOfOptions = true;
593
602
  return [];
@@ -599,7 +608,7 @@ export function bashTargets(command: string, home: string, cwd: string): PathTar
599
608
  // mv changes its sources too, so every mv operand counts as a write.
600
609
  const everyOperand =
601
610
  stage.op === "delete" ||
602
- /(?:^|\s)(?:mkdir|mkfile|touch|tee|truncate|install|mv)(?:\s|$)/.test(stage.commandPart) ||
611
+ /(?:^|\s)\\?(?:[^\s;&|<>()]*\/)?(?:mkdir|mkfile|touch|tee|truncate|install|mv)(?:\s|$)/.test(stage.commandPart) ||
603
612
  (/(?:^|\s)sed(?:\s|$)/.test(stage.commandPart) && /(?:^|\s)(?:-i\S*|--in-place\S*)(?:\s|$)/.test(stage.commandPart));
604
613
  const operandTargets: Array<{ operand: string; op: Operation }> = [];
605
614
  if (everyOperand) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/hooks",
3
- "version": "0.10.8",
3
+ "version": "0.10.9",
4
4
  "description": "Open source hooks library for AI coding agents - Install safety, quality, and automation hooks with a single command",
5
5
  "type": "module",
6
6
  "bin": {