@codyswann/lisa 2.163.4 → 2.163.5

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 (59) hide show
  1. package/dist/codex/scripts/block-no-verify.sh +63 -9
  2. package/package.json +1 -1
  3. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  4. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  5. package/plugins/lisa/hooks/block-no-verify.agy.sh +62 -13
  6. package/plugins/lisa/hooks/block-no-verify.sh +62 -10
  7. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +62 -13
  8. package/plugins/lisa-agy/plugin.json +1 -1
  9. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  12. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-copilot/hooks/block-no-verify.sh +62 -10
  16. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cursor/hooks/block-no-verify.sh +62 -10
  18. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-expo-agy/plugin.json +1 -1
  21. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  26. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  31. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  36. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  41. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-rails-agy/plugin.json +1 -1
  46. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  51. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  56. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/src/base/hooks/block-no-verify.agy.sh +62 -13
  59. package/plugins/src/base/hooks/block-no-verify.sh +62 -10
@@ -2,10 +2,10 @@
2
2
  # Lisa-managed Codex hook script (PreToolUse Bash).
3
3
  # Blocks git commands that bypass verification hooks: the --no-verify long flag,
4
4
  # HUSKY=0 / HUSKY_SKIP_HOOKS= (disables husky hooks), and core.hooksPath pointed
5
- # at /dev/null or set empty (disables all git hooks). The short `-n` form is
6
- # intentionally NOT matched (parity with block-no-verify.sh / .agy.sh): grep
7
- # cannot distinguish it from -n in commit-message prose or an unrelated piped
8
- # command, and -n is far more common than --no-verify.
5
+ # at /dev/null or set empty (disables all git hooks). Shell-token matching
6
+ # avoids false positives from issue bodies, heredocs, and commit-message prose
7
+ # while still catching quoted real argv values such as
8
+ # `git -c "core.hooksPath=/dev/null"`.
9
9
  set -euo pipefail
10
10
 
11
11
  input="$(cat 2>/dev/null || true)"
@@ -18,11 +18,65 @@ tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null || t
18
18
  command_str="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || true)"
19
19
  [ -n "$command_str" ] || exit 0
20
20
 
21
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
22
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
23
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
24
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
25
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
21
+ command -v python3 >/dev/null 2>&1 || exit 0
22
+
23
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
24
+ import os
25
+ import re
26
+ import shlex
27
+ import sys
28
+
29
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
30
+
31
+
32
+ def strip_heredocs(text: str) -> str:
33
+ lines = text.splitlines()
34
+ output = []
35
+ pending = []
36
+ marker_pattern = re.compile(
37
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
38
+ )
39
+ index = 0
40
+ while index < len(lines):
41
+ line = lines[index]
42
+ output.append(line)
43
+ pending.extend(
44
+ next(group for group in match.groups() if group)
45
+ for match in marker_pattern.finditer(line)
46
+ )
47
+ index += 1
48
+ while pending and index < len(lines):
49
+ if lines[index].strip() == pending[0]:
50
+ output.append(lines[index])
51
+ pending.pop(0)
52
+ index += 1
53
+ break
54
+ index += 1
55
+ return "\n".join(output)
56
+
57
+
58
+ try:
59
+ tokens = shlex.split(strip_heredocs(command), posix=True)
60
+ except ValueError:
61
+ sys.exit(0)
62
+
63
+ normalized_tokens = [token.strip("();|&") for token in tokens]
64
+
65
+ for i, token in enumerate(normalized_tokens):
66
+ if token == "--no-verify":
67
+ sys.exit(1)
68
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
69
+ sys.exit(1)
70
+ if token.startswith("core.hooksPath="):
71
+ value = token.split("=", 1)[1]
72
+ if value in ("", "/dev/null"):
73
+ sys.exit(1)
74
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
75
+ sys.exit(1)
76
+
77
+ sys.exit(0)
78
+ PY
79
+ then
26
80
  jq -n '{
27
81
  "hookSpecificOutput": {
28
82
  "hookEventName": "PreToolUse",
package/package.json CHANGED
@@ -85,7 +85,7 @@
85
85
  "lodash": ">=4.18.1"
86
86
  },
87
87
  "name": "@codyswann/lisa",
88
- "version": "2.163.4",
88
+ "version": "2.163.5",
89
89
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
90
90
  "main": "dist/index.js",
91
91
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -3,8 +3,9 @@
3
3
  # git quality gates (exact parity with the Claude block-no-verify.sh): the
4
4
  # `--no-verify` long flag, `HUSKY=0`/`HUSKY_SKIP_HOOKS=` (disables husky hooks),
5
5
  # and `core.hooksPath` pointed at /dev/null or set empty (disables all git
6
- # hooks). The short `-n` form is intentionally NOT matched, to avoid false
7
- # positives (see the matching block below).
6
+ # hooks). Shell-token matching avoids false positives from issue bodies,
7
+ # heredocs, and commit-message prose while still catching quoted real argv
8
+ # values such as `git -c "core.hooksPath=/dev/null"`.
8
9
  #
9
10
  # agy protocol (distinct from the Claude block-no-verify.sh exit-code protocol):
10
11
  # - stdin = JSON: { "toolCall": { "name": "run_command",
@@ -34,17 +35,65 @@ input="$(cat 2>/dev/null || true)"
34
35
  command_str="$(printf '%s' "$input" | jq -r '.toolCall.args.CommandLine // empty' 2>/dev/null || true)"
35
36
  [ -z "$command_str" ] && allow
36
37
 
37
- # Each pattern is bounded so longer flags (--no-verify-ssl) and legit values
38
- # (core.hooksPath=.husky, HUSKY=1) don't match, while every syntactic position
39
- # is caught (incl. subshells). Exact parity with the Claude block-no-verify.sh.
40
- # The short `-n` form is intentionally NOT matched — `-n` appears in commit
41
- # message prose (`git commit -m "fix -n flag"`) and unrelated piped commands
42
- # (`sort -n x; git commit`), so guarding it false-positives on valid work.
43
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
44
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
45
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
46
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
47
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
38
+ command -v python3 >/dev/null 2>&1 || allow
39
+
40
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
41
+ import os
42
+ import re
43
+ import shlex
44
+ import sys
45
+
46
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
47
+
48
+
49
+ def strip_heredocs(text: str) -> str:
50
+ lines = text.splitlines()
51
+ output = []
52
+ pending = []
53
+ marker_pattern = re.compile(
54
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
55
+ )
56
+ index = 0
57
+ while index < len(lines):
58
+ line = lines[index]
59
+ output.append(line)
60
+ pending.extend(
61
+ next(group for group in match.groups() if group)
62
+ for match in marker_pattern.finditer(line)
63
+ )
64
+ index += 1
65
+ while pending and index < len(lines):
66
+ if lines[index].strip() == pending[0]:
67
+ output.append(lines[index])
68
+ pending.pop(0)
69
+ index += 1
70
+ break
71
+ index += 1
72
+ return "\n".join(output)
73
+
74
+
75
+ try:
76
+ tokens = shlex.split(strip_heredocs(command), posix=True)
77
+ except ValueError:
78
+ sys.exit(0)
79
+
80
+ normalized_tokens = [token.strip("();|&") for token in tokens]
81
+
82
+ for i, token in enumerate(normalized_tokens):
83
+ if token == "--no-verify":
84
+ sys.exit(1)
85
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
86
+ sys.exit(1)
87
+ if token.startswith("core.hooksPath="):
88
+ value = token.split("=", 1)[1]
89
+ if value in ("", "/dev/null"):
90
+ sys.exit(1)
91
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
92
+ sys.exit(1)
93
+
94
+ sys.exit(0)
95
+ PY
96
+ then
48
97
  deny
49
98
  fi
50
99
 
@@ -9,8 +9,9 @@
9
9
  # 2. HUSKY=0 / HUSKY_SKIP_HOOKS=... — disables husky-managed git hooks;
10
10
  # 3. core.hooksPath pointed at /dev/null or set empty — disables ALL git hooks.
11
11
  #
12
- # Word-boundary matching avoids false positives on longer flags (--no-verify-ssl,
13
- # --no-verify-host) and on a legit custom hooks path (core.hooksPath=.husky).
12
+ # Shell-token matching avoids false positives from issue bodies, heredocs, and
13
+ # commit-message prose while still catching quoted real argv values such as
14
+ # `git -c "core.hooksPath=/dev/null"`.
14
15
  #
15
16
  # The short `-n` form is intentionally NOT matched (see block-no-verify.agy.sh):
16
17
  # grep cannot distinguish a real -n option from -n in commit-message prose or an
@@ -29,14 +30,65 @@ if [ -z "$command_str" ]; then
29
30
  exit 0
30
31
  fi
31
32
 
32
- # Each pattern is bounded by non-token characters so longer flags
33
- # (--no-verify-ssl) and legit values (core.hooksPath=.husky, HUSKY=1) don't match,
34
- # while every syntactic position is caught (incl. subshells, e.g. `(git commit --no-verify)`).
35
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
36
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
37
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
38
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
39
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
33
+ command -v python3 >/dev/null 2>&1 || exit 0
34
+
35
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
36
+ import os
37
+ import re
38
+ import shlex
39
+ import sys
40
+
41
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
42
+
43
+
44
+ def strip_heredocs(text: str) -> str:
45
+ lines = text.splitlines()
46
+ output = []
47
+ pending = []
48
+ marker_pattern = re.compile(
49
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
50
+ )
51
+ index = 0
52
+ while index < len(lines):
53
+ line = lines[index]
54
+ output.append(line)
55
+ pending.extend(
56
+ next(group for group in match.groups() if group)
57
+ for match in marker_pattern.finditer(line)
58
+ )
59
+ index += 1
60
+ while pending and index < len(lines):
61
+ if lines[index].strip() == pending[0]:
62
+ output.append(lines[index])
63
+ pending.pop(0)
64
+ index += 1
65
+ break
66
+ index += 1
67
+ return "\n".join(output)
68
+
69
+
70
+ try:
71
+ tokens = shlex.split(strip_heredocs(command), posix=True)
72
+ except ValueError:
73
+ sys.exit(0)
74
+
75
+ normalized_tokens = [token.strip("();|&") for token in tokens]
76
+
77
+ for i, token in enumerate(normalized_tokens):
78
+ if token == "--no-verify":
79
+ sys.exit(1)
80
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
81
+ sys.exit(1)
82
+ if token.startswith("core.hooksPath="):
83
+ value = token.split("=", 1)[1]
84
+ if value in ("", "/dev/null"):
85
+ sys.exit(1)
86
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
87
+ sys.exit(1)
88
+
89
+ sys.exit(0)
90
+ PY
91
+ then
40
92
  cat >&2 <<'EOF'
41
93
  Blocked: this command bypasses pre-commit/pre-push hooks (--no-verify, HUSKY=0,
42
94
  or core.hooksPath disabling). Fix the underlying issue (lint error, failing
@@ -3,8 +3,9 @@
3
3
  # git quality gates (exact parity with the Claude block-no-verify.sh): the
4
4
  # `--no-verify` long flag, `HUSKY=0`/`HUSKY_SKIP_HOOKS=` (disables husky hooks),
5
5
  # and `core.hooksPath` pointed at /dev/null or set empty (disables all git
6
- # hooks). The short `-n` form is intentionally NOT matched, to avoid false
7
- # positives (see the matching block below).
6
+ # hooks). Shell-token matching avoids false positives from issue bodies,
7
+ # heredocs, and commit-message prose while still catching quoted real argv
8
+ # values such as `git -c "core.hooksPath=/dev/null"`.
8
9
  #
9
10
  # agy protocol (distinct from the Claude block-no-verify.sh exit-code protocol):
10
11
  # - stdin = JSON: { "toolCall": { "name": "run_command",
@@ -34,17 +35,65 @@ input="$(cat 2>/dev/null || true)"
34
35
  command_str="$(printf '%s' "$input" | jq -r '.toolCall.args.CommandLine // empty' 2>/dev/null || true)"
35
36
  [ -z "$command_str" ] && allow
36
37
 
37
- # Each pattern is bounded so longer flags (--no-verify-ssl) and legit values
38
- # (core.hooksPath=.husky, HUSKY=1) don't match, while every syntactic position
39
- # is caught (incl. subshells). Exact parity with the Claude block-no-verify.sh.
40
- # The short `-n` form is intentionally NOT matched — `-n` appears in commit
41
- # message prose (`git commit -m "fix -n flag"`) and unrelated piped commands
42
- # (`sort -n x; git commit`), so guarding it false-positives on valid work.
43
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
44
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
45
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
46
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
47
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
38
+ command -v python3 >/dev/null 2>&1 || allow
39
+
40
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
41
+ import os
42
+ import re
43
+ import shlex
44
+ import sys
45
+
46
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
47
+
48
+
49
+ def strip_heredocs(text: str) -> str:
50
+ lines = text.splitlines()
51
+ output = []
52
+ pending = []
53
+ marker_pattern = re.compile(
54
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
55
+ )
56
+ index = 0
57
+ while index < len(lines):
58
+ line = lines[index]
59
+ output.append(line)
60
+ pending.extend(
61
+ next(group for group in match.groups() if group)
62
+ for match in marker_pattern.finditer(line)
63
+ )
64
+ index += 1
65
+ while pending and index < len(lines):
66
+ if lines[index].strip() == pending[0]:
67
+ output.append(lines[index])
68
+ pending.pop(0)
69
+ index += 1
70
+ break
71
+ index += 1
72
+ return "\n".join(output)
73
+
74
+
75
+ try:
76
+ tokens = shlex.split(strip_heredocs(command), posix=True)
77
+ except ValueError:
78
+ sys.exit(0)
79
+
80
+ normalized_tokens = [token.strip("();|&") for token in tokens]
81
+
82
+ for i, token in enumerate(normalized_tokens):
83
+ if token == "--no-verify":
84
+ sys.exit(1)
85
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
86
+ sys.exit(1)
87
+ if token.startswith("core.hooksPath="):
88
+ value = token.split("=", 1)[1]
89
+ if value in ("", "/dev/null"):
90
+ sys.exit(1)
91
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
92
+ sys.exit(1)
93
+
94
+ sys.exit(0)
95
+ PY
96
+ then
48
97
  deny
49
98
  fi
50
99
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -9,8 +9,9 @@
9
9
  # 2. HUSKY=0 / HUSKY_SKIP_HOOKS=... — disables husky-managed git hooks;
10
10
  # 3. core.hooksPath pointed at /dev/null or set empty — disables ALL git hooks.
11
11
  #
12
- # Word-boundary matching avoids false positives on longer flags (--no-verify-ssl,
13
- # --no-verify-host) and on a legit custom hooks path (core.hooksPath=.husky).
12
+ # Shell-token matching avoids false positives from issue bodies, heredocs, and
13
+ # commit-message prose while still catching quoted real argv values such as
14
+ # `git -c "core.hooksPath=/dev/null"`.
14
15
  #
15
16
  # The short `-n` form is intentionally NOT matched (see block-no-verify.agy.sh):
16
17
  # grep cannot distinguish a real -n option from -n in commit-message prose or an
@@ -29,14 +30,65 @@ if [ -z "$command_str" ]; then
29
30
  exit 0
30
31
  fi
31
32
 
32
- # Each pattern is bounded by non-token characters so longer flags
33
- # (--no-verify-ssl) and legit values (core.hooksPath=.husky, HUSKY=1) don't match,
34
- # while every syntactic position is caught (incl. subshells, e.g. `(git commit --no-verify)`).
35
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
36
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
37
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
38
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
39
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
33
+ command -v python3 >/dev/null 2>&1 || exit 0
34
+
35
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
36
+ import os
37
+ import re
38
+ import shlex
39
+ import sys
40
+
41
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
42
+
43
+
44
+ def strip_heredocs(text: str) -> str:
45
+ lines = text.splitlines()
46
+ output = []
47
+ pending = []
48
+ marker_pattern = re.compile(
49
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
50
+ )
51
+ index = 0
52
+ while index < len(lines):
53
+ line = lines[index]
54
+ output.append(line)
55
+ pending.extend(
56
+ next(group for group in match.groups() if group)
57
+ for match in marker_pattern.finditer(line)
58
+ )
59
+ index += 1
60
+ while pending and index < len(lines):
61
+ if lines[index].strip() == pending[0]:
62
+ output.append(lines[index])
63
+ pending.pop(0)
64
+ index += 1
65
+ break
66
+ index += 1
67
+ return "\n".join(output)
68
+
69
+
70
+ try:
71
+ tokens = shlex.split(strip_heredocs(command), posix=True)
72
+ except ValueError:
73
+ sys.exit(0)
74
+
75
+ normalized_tokens = [token.strip("();|&") for token in tokens]
76
+
77
+ for i, token in enumerate(normalized_tokens):
78
+ if token == "--no-verify":
79
+ sys.exit(1)
80
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
81
+ sys.exit(1)
82
+ if token.startswith("core.hooksPath="):
83
+ value = token.split("=", 1)[1]
84
+ if value in ("", "/dev/null"):
85
+ sys.exit(1)
86
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
87
+ sys.exit(1)
88
+
89
+ sys.exit(0)
90
+ PY
91
+ then
40
92
  cat >&2 <<'EOF'
41
93
  Blocked: this command bypasses pre-commit/pre-push hooks (--no-verify, HUSKY=0,
42
94
  or core.hooksPath disabling). Fix the underlying issue (lint error, failing
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -9,8 +9,9 @@
9
9
  # 2. HUSKY=0 / HUSKY_SKIP_HOOKS=... — disables husky-managed git hooks;
10
10
  # 3. core.hooksPath pointed at /dev/null or set empty — disables ALL git hooks.
11
11
  #
12
- # Word-boundary matching avoids false positives on longer flags (--no-verify-ssl,
13
- # --no-verify-host) and on a legit custom hooks path (core.hooksPath=.husky).
12
+ # Shell-token matching avoids false positives from issue bodies, heredocs, and
13
+ # commit-message prose while still catching quoted real argv values such as
14
+ # `git -c "core.hooksPath=/dev/null"`.
14
15
  #
15
16
  # The short `-n` form is intentionally NOT matched (see block-no-verify.agy.sh):
16
17
  # grep cannot distinguish a real -n option from -n in commit-message prose or an
@@ -29,14 +30,65 @@ if [ -z "$command_str" ]; then
29
30
  exit 0
30
31
  fi
31
32
 
32
- # Each pattern is bounded by non-token characters so longer flags
33
- # (--no-verify-ssl) and legit values (core.hooksPath=.husky, HUSKY=1) don't match,
34
- # while every syntactic position is caught (incl. subshells, e.g. `(git commit --no-verify)`).
35
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
36
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
37
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
38
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
39
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
33
+ command -v python3 >/dev/null 2>&1 || exit 0
34
+
35
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
36
+ import os
37
+ import re
38
+ import shlex
39
+ import sys
40
+
41
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
42
+
43
+
44
+ def strip_heredocs(text: str) -> str:
45
+ lines = text.splitlines()
46
+ output = []
47
+ pending = []
48
+ marker_pattern = re.compile(
49
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
50
+ )
51
+ index = 0
52
+ while index < len(lines):
53
+ line = lines[index]
54
+ output.append(line)
55
+ pending.extend(
56
+ next(group for group in match.groups() if group)
57
+ for match in marker_pattern.finditer(line)
58
+ )
59
+ index += 1
60
+ while pending and index < len(lines):
61
+ if lines[index].strip() == pending[0]:
62
+ output.append(lines[index])
63
+ pending.pop(0)
64
+ index += 1
65
+ break
66
+ index += 1
67
+ return "\n".join(output)
68
+
69
+
70
+ try:
71
+ tokens = shlex.split(strip_heredocs(command), posix=True)
72
+ except ValueError:
73
+ sys.exit(0)
74
+
75
+ normalized_tokens = [token.strip("();|&") for token in tokens]
76
+
77
+ for i, token in enumerate(normalized_tokens):
78
+ if token == "--no-verify":
79
+ sys.exit(1)
80
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
81
+ sys.exit(1)
82
+ if token.startswith("core.hooksPath="):
83
+ value = token.split("=", 1)[1]
84
+ if value in ("", "/dev/null"):
85
+ sys.exit(1)
86
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
87
+ sys.exit(1)
88
+
89
+ sys.exit(0)
90
+ PY
91
+ then
40
92
  cat >&2 <<'EOF'
41
93
  Blocked: this command bypasses pre-commit/pre-push hooks (--no-verify, HUSKY=0,
42
94
  or core.hooksPath disabling). Fix the underlying issue (lint error, failing
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.163.4",
3
+ "version": "2.163.5",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -3,8 +3,9 @@
3
3
  # git quality gates (exact parity with the Claude block-no-verify.sh): the
4
4
  # `--no-verify` long flag, `HUSKY=0`/`HUSKY_SKIP_HOOKS=` (disables husky hooks),
5
5
  # and `core.hooksPath` pointed at /dev/null or set empty (disables all git
6
- # hooks). The short `-n` form is intentionally NOT matched, to avoid false
7
- # positives (see the matching block below).
6
+ # hooks). Shell-token matching avoids false positives from issue bodies,
7
+ # heredocs, and commit-message prose while still catching quoted real argv
8
+ # values such as `git -c "core.hooksPath=/dev/null"`.
8
9
  #
9
10
  # agy protocol (distinct from the Claude block-no-verify.sh exit-code protocol):
10
11
  # - stdin = JSON: { "toolCall": { "name": "run_command",
@@ -34,17 +35,65 @@ input="$(cat 2>/dev/null || true)"
34
35
  command_str="$(printf '%s' "$input" | jq -r '.toolCall.args.CommandLine // empty' 2>/dev/null || true)"
35
36
  [ -z "$command_str" ] && allow
36
37
 
37
- # Each pattern is bounded so longer flags (--no-verify-ssl) and legit values
38
- # (core.hooksPath=.husky, HUSKY=1) don't match, while every syntactic position
39
- # is caught (incl. subshells). Exact parity with the Claude block-no-verify.sh.
40
- # The short `-n` form is intentionally NOT matched — `-n` appears in commit
41
- # message prose (`git commit -m "fix -n flag"`) and unrelated piped commands
42
- # (`sort -n x; git commit`), so guarding it false-positives on valid work.
43
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
44
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
45
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
46
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
47
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
38
+ command -v python3 >/dev/null 2>&1 || allow
39
+
40
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
41
+ import os
42
+ import re
43
+ import shlex
44
+ import sys
45
+
46
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
47
+
48
+
49
+ def strip_heredocs(text: str) -> str:
50
+ lines = text.splitlines()
51
+ output = []
52
+ pending = []
53
+ marker_pattern = re.compile(
54
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
55
+ )
56
+ index = 0
57
+ while index < len(lines):
58
+ line = lines[index]
59
+ output.append(line)
60
+ pending.extend(
61
+ next(group for group in match.groups() if group)
62
+ for match in marker_pattern.finditer(line)
63
+ )
64
+ index += 1
65
+ while pending and index < len(lines):
66
+ if lines[index].strip() == pending[0]:
67
+ output.append(lines[index])
68
+ pending.pop(0)
69
+ index += 1
70
+ break
71
+ index += 1
72
+ return "\n".join(output)
73
+
74
+
75
+ try:
76
+ tokens = shlex.split(strip_heredocs(command), posix=True)
77
+ except ValueError:
78
+ sys.exit(0)
79
+
80
+ normalized_tokens = [token.strip("();|&") for token in tokens]
81
+
82
+ for i, token in enumerate(normalized_tokens):
83
+ if token == "--no-verify":
84
+ sys.exit(1)
85
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
86
+ sys.exit(1)
87
+ if token.startswith("core.hooksPath="):
88
+ value = token.split("=", 1)[1]
89
+ if value in ("", "/dev/null"):
90
+ sys.exit(1)
91
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
92
+ sys.exit(1)
93
+
94
+ sys.exit(0)
95
+ PY
96
+ then
48
97
  deny
49
98
  fi
50
99
 
@@ -9,8 +9,9 @@
9
9
  # 2. HUSKY=0 / HUSKY_SKIP_HOOKS=... — disables husky-managed git hooks;
10
10
  # 3. core.hooksPath pointed at /dev/null or set empty — disables ALL git hooks.
11
11
  #
12
- # Word-boundary matching avoids false positives on longer flags (--no-verify-ssl,
13
- # --no-verify-host) and on a legit custom hooks path (core.hooksPath=.husky).
12
+ # Shell-token matching avoids false positives from issue bodies, heredocs, and
13
+ # commit-message prose while still catching quoted real argv values such as
14
+ # `git -c "core.hooksPath=/dev/null"`.
14
15
  #
15
16
  # The short `-n` form is intentionally NOT matched (see block-no-verify.agy.sh):
16
17
  # grep cannot distinguish a real -n option from -n in commit-message prose or an
@@ -29,14 +30,65 @@ if [ -z "$command_str" ]; then
29
30
  exit 0
30
31
  fi
31
32
 
32
- # Each pattern is bounded by non-token characters so longer flags
33
- # (--no-verify-ssl) and legit values (core.hooksPath=.husky, HUSKY=1) don't match,
34
- # while every syntactic position is caught (incl. subshells, e.g. `(git commit --no-verify)`).
35
- if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])--no-verify($|[^[:alnum:]_-])' \
36
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY=0($|[^[:alnum:]])' \
37
- || printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])HUSKY_SKIP_HOOKS=' \
38
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath([[:space:]]*=)?[[:space:]]*/dev/null' \
39
- || printf '%s' "$command_str" | grep -Eq 'core\.hooksPath[[:space:]]*=[[:space:]]*($|[[:space:];&|"'\''])'; then
33
+ command -v python3 >/dev/null 2>&1 || exit 0
34
+
35
+ if ! BLOCK_NO_VERIFY_COMMAND="$command_str" python3 - <<'PY'
36
+ import os
37
+ import re
38
+ import shlex
39
+ import sys
40
+
41
+ command = os.environ.get("BLOCK_NO_VERIFY_COMMAND", "")
42
+
43
+
44
+ def strip_heredocs(text: str) -> str:
45
+ lines = text.splitlines()
46
+ output = []
47
+ pending = []
48
+ marker_pattern = re.compile(
49
+ r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|([A-Za-z_][A-Za-z0-9_]*))"
50
+ )
51
+ index = 0
52
+ while index < len(lines):
53
+ line = lines[index]
54
+ output.append(line)
55
+ pending.extend(
56
+ next(group for group in match.groups() if group)
57
+ for match in marker_pattern.finditer(line)
58
+ )
59
+ index += 1
60
+ while pending and index < len(lines):
61
+ if lines[index].strip() == pending[0]:
62
+ output.append(lines[index])
63
+ pending.pop(0)
64
+ index += 1
65
+ break
66
+ index += 1
67
+ return "\n".join(output)
68
+
69
+
70
+ try:
71
+ tokens = shlex.split(strip_heredocs(command), posix=True)
72
+ except ValueError:
73
+ sys.exit(0)
74
+
75
+ normalized_tokens = [token.strip("();|&") for token in tokens]
76
+
77
+ for i, token in enumerate(normalized_tokens):
78
+ if token == "--no-verify":
79
+ sys.exit(1)
80
+ if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
81
+ sys.exit(1)
82
+ if token.startswith("core.hooksPath="):
83
+ value = token.split("=", 1)[1]
84
+ if value in ("", "/dev/null"):
85
+ sys.exit(1)
86
+ if token == "core.hooksPath" and i + 1 < len(normalized_tokens) and normalized_tokens[i + 1] in ("", "/dev/null"):
87
+ sys.exit(1)
88
+
89
+ sys.exit(0)
90
+ PY
91
+ then
40
92
  cat >&2 <<'EOF'
41
93
  Blocked: this command bypasses pre-commit/pre-push hooks (--no-verify, HUSKY=0,
42
94
  or core.hooksPath disabling). Fix the underlying issue (lint error, failing