@codyswann/lisa 2.250.0 → 2.251.0
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/opencode/plugin-templates/parity-safety-net.sh +16 -4
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +5 -1
- package/plugins/lisa/.codex-plugin/hooks.json +4 -0
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-shell-json-parsing.agy.sh +52 -0
- package/plugins/lisa/hooks/block-shell-json-parsing.sh +220 -0
- package/plugins/lisa/hooks/parity-safety-net.sh +16 -4
- package/plugins/lisa-agy/hooks/block-shell-json-parsing.agy.sh +52 -0
- package/plugins/lisa-agy/hooks/block-shell-json-parsing.sh +220 -0
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +16 -4
- package/plugins/lisa-agy/hooks.json +13 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +5 -1
- package/plugins/lisa-copilot/hooks/block-shell-json-parsing.sh +220 -0
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +16 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-shell-json-parsing.sh +220 -0
- package/plugins/lisa-cursor/hooks/hooks.json +4 -0
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +16 -4
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/.claude-plugin/plugin.json +4 -0
- package/plugins/src/base/hooks/block-shell-json-parsing.agy.sh +52 -0
- package/plugins/src/base/hooks/block-shell-json-parsing.sh +220 -0
- package/plugins/src/base/hooks/parity-safety-net.sh +16 -4
- package/scripts/generate-agy-plugin-artifacts.mjs +8 -0
- package/scripts/lib/per-agent-hook-filter.mjs +7 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Antigravity (agy) PreToolUse adapter for Lisa's canonical shell-JSON guard.
|
|
3
|
+
#
|
|
4
|
+
# agy sends `{toolCall:{name:"run_command",args:{CommandLine:"..."}}}` and
|
|
5
|
+
# expects an allow/deny JSON object on stdout. The canonical hook consumes the
|
|
6
|
+
# Claude Bash-hook envelope and communicates denial with exit status 2. This
|
|
7
|
+
# adapter only translates protocols; all classification stays in the canonical
|
|
8
|
+
# block-shell-json-parsing.sh beside it (same delegation pattern as
|
|
9
|
+
# parity-safety-net.agy.sh). Fail-open on missing runtimes: unlike the
|
|
10
|
+
# safety net, a missed nudge here risks a fragile script, not data loss.
|
|
11
|
+
set -uo pipefail
|
|
12
|
+
|
|
13
|
+
allow() {
|
|
14
|
+
printf '%s\n' '{"decision":"allow"}'
|
|
15
|
+
exit 0
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
deny() {
|
|
19
|
+
local reason="$1"
|
|
20
|
+
if command -v jq >/dev/null 2>&1; then
|
|
21
|
+
jq -cn --arg reason "$reason" '{decision:"deny",reason:$reason}'
|
|
22
|
+
else
|
|
23
|
+
printf '%s\n' '{"decision":"allow"}'
|
|
24
|
+
fi
|
|
25
|
+
exit 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
input="$(cat 2>/dev/null || true)"
|
|
29
|
+
[ -z "$input" ] && allow
|
|
30
|
+
command -v jq >/dev/null 2>&1 || allow
|
|
31
|
+
|
|
32
|
+
tool_name="$(printf '%s' "$input" | jq -r '.toolCall.name // empty' 2>/dev/null || true)"
|
|
33
|
+
[ "$tool_name" != "run_command" ] && allow
|
|
34
|
+
command_str="$(printf '%s' "$input" | jq -r '.toolCall.args.CommandLine // empty' 2>/dev/null || true)"
|
|
35
|
+
[ -z "$command_str" ] && allow
|
|
36
|
+
|
|
37
|
+
hook_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
canonical_hook="$hook_dir/block-shell-json-parsing.sh"
|
|
39
|
+
[ -r "$canonical_hook" ] || allow
|
|
40
|
+
|
|
41
|
+
canonical_input="$(jq -cn --arg command "$command_str" '{tool_name:"Bash",tool_input:{command:$command}}')"
|
|
42
|
+
canonical_output=""
|
|
43
|
+
canonical_status=0
|
|
44
|
+
if canonical_output="$(printf '%s' "$canonical_input" | /bin/bash "$canonical_hook" 2>&1)"; then
|
|
45
|
+
canonical_status=0
|
|
46
|
+
else
|
|
47
|
+
canonical_status=$?
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
[ "$canonical_status" -eq 0 ] && allow
|
|
51
|
+
[ -n "$canonical_output" ] || canonical_output="Blocked: structural JSON parsing with text tools; use jq."
|
|
52
|
+
deny "$canonical_output"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PreToolUse hook for Bash: blocks structural JSON parsing with text tools.
|
|
3
|
+
# Text tools (grep/sed/cut/awk) break on valid JSON — multiline values, escaped
|
|
4
|
+
# quotes, reordered keys, nested objects — producing silently wrong output
|
|
5
|
+
# instead of errors. jq is the required tool for structural JSON reads and
|
|
6
|
+
# writes in shell. Promoted from PROJECT_RULES.md prose to an executable
|
|
7
|
+
# control by the learnings gardener (issue #1787).
|
|
8
|
+
#
|
|
9
|
+
# Precision-first: this hook fires only on high-confidence STRUCTURAL parsing
|
|
10
|
+
# of a *.json input — never on plain text search. Blocked signatures:
|
|
11
|
+
# 1. `sed -i` (or an s/…/…/ program) applied to a .json file or stream;
|
|
12
|
+
# 2. `cut -d` with a structural delimiter (quote/colon/comma) on .json input;
|
|
13
|
+
# 3. `awk` field extraction (-F or a $N program) on .json input;
|
|
14
|
+
# 4. `grep -o` on .json input (value extraction, not search).
|
|
15
|
+
# A "json input" is a *.json argument, a `< file.json` redirection, or a
|
|
16
|
+
# pipeline stream originating from a .json file (e.g. `cat x.json | …`).
|
|
17
|
+
#
|
|
18
|
+
# Exemptions (allowed):
|
|
19
|
+
# - any command that invokes jq anywhere (already compliant or mixed-legit);
|
|
20
|
+
# - search-only usage: plain grep / grep -l / rg with no extraction signature;
|
|
21
|
+
# - *.jsonl targets (line-delimited streams are legitimately line-tooled);
|
|
22
|
+
# - heredoc payload text (stripped before classification).
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
input="$(cat)"
|
|
26
|
+
|
|
27
|
+
tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
|
|
28
|
+
if [ "$tool_name" != "Bash" ]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
command_str="$(printf '%s' "$input" | jq -r '.tool_input.command // empty')"
|
|
33
|
+
if [ -z "$command_str" ]; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Fast path: no ".json" reference at all means nothing to classify.
|
|
38
|
+
case "$command_str" in
|
|
39
|
+
*.json*) ;;
|
|
40
|
+
*) exit 0 ;;
|
|
41
|
+
esac
|
|
42
|
+
|
|
43
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
44
|
+
|
|
45
|
+
if ! BLOCK_SHELL_JSON_COMMAND="$command_str" python3 - <<'PY'
|
|
46
|
+
import os
|
|
47
|
+
import re
|
|
48
|
+
import shlex
|
|
49
|
+
import sys
|
|
50
|
+
|
|
51
|
+
command = os.environ.get("BLOCK_SHELL_JSON_COMMAND", "")
|
|
52
|
+
|
|
53
|
+
TEXT_TOOLS = {"grep", "egrep", "fgrep", "sed", "gsed", "awk", "gawk", "cut"}
|
|
54
|
+
WRAPPERS = {"command", "env", "sudo", "time", "nohup"}
|
|
55
|
+
ASSIGNMENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def strip_heredoc_bodies(text: str) -> str:
|
|
59
|
+
"""Remove heredoc payload lines so prose .json mentions never classify."""
|
|
60
|
+
lines = text.splitlines()
|
|
61
|
+
output = []
|
|
62
|
+
marker_pattern = re.compile(
|
|
63
|
+
r"<<-?\s*(?:'([^']+)'|\"([^\"]+)\"|\\?([A-Za-z_][A-Za-z0-9_]*))"
|
|
64
|
+
)
|
|
65
|
+
index = 0
|
|
66
|
+
while index < len(lines):
|
|
67
|
+
line = lines[index]
|
|
68
|
+
output.append(line)
|
|
69
|
+
markers = [
|
|
70
|
+
next(group for group in match.groups() if group)
|
|
71
|
+
for match in marker_pattern.finditer(line)
|
|
72
|
+
]
|
|
73
|
+
index += 1
|
|
74
|
+
for marker in markers:
|
|
75
|
+
while index < len(lines):
|
|
76
|
+
if lines[index].strip() == marker:
|
|
77
|
+
index += 1
|
|
78
|
+
break
|
|
79
|
+
index += 1
|
|
80
|
+
return "\n".join(output)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def tokenize(text: str):
|
|
84
|
+
lex = shlex.shlex(text, posix=True, punctuation_chars="|&;<>")
|
|
85
|
+
lex.whitespace_split = True
|
|
86
|
+
try:
|
|
87
|
+
return list(lex)
|
|
88
|
+
except ValueError:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def is_json_path(token: str) -> bool:
|
|
93
|
+
return token.lower().rstrip(")};").endswith(".json")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def split_pipelines(tokens):
|
|
97
|
+
"""Yield lists of pipe-connected segments; each segment is a token list."""
|
|
98
|
+
pipelines, pipeline, segment = [], [], []
|
|
99
|
+
for token in tokens:
|
|
100
|
+
if token in ("|", "|&"):
|
|
101
|
+
pipeline.append(segment)
|
|
102
|
+
segment = []
|
|
103
|
+
elif token in (";", "&&", "||", "&", "\n"):
|
|
104
|
+
pipeline.append(segment)
|
|
105
|
+
pipelines.append(pipeline)
|
|
106
|
+
pipeline, segment = [], []
|
|
107
|
+
else:
|
|
108
|
+
segment.append(token)
|
|
109
|
+
pipeline.append(segment)
|
|
110
|
+
pipelines.append(pipeline)
|
|
111
|
+
return pipelines
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def segment_parts(segment):
|
|
115
|
+
"""Return (tool, args, reads_json) for one pipeline segment."""
|
|
116
|
+
reads_json = False
|
|
117
|
+
tool = None
|
|
118
|
+
args = []
|
|
119
|
+
index = 0
|
|
120
|
+
while index < len(segment):
|
|
121
|
+
token = segment[index]
|
|
122
|
+
if token == "<":
|
|
123
|
+
if index + 1 < len(segment) and is_json_path(segment[index + 1]):
|
|
124
|
+
reads_json = True
|
|
125
|
+
index += 2
|
|
126
|
+
continue
|
|
127
|
+
if token in (">", ">>", "<<", "<<<"):
|
|
128
|
+
index += 2
|
|
129
|
+
continue
|
|
130
|
+
if tool is None and (ASSIGNMENT.match(token) or token in WRAPPERS):
|
|
131
|
+
index += 1
|
|
132
|
+
continue
|
|
133
|
+
if tool is None:
|
|
134
|
+
tool = token.rsplit("/", 1)[-1]
|
|
135
|
+
else:
|
|
136
|
+
args.append(token)
|
|
137
|
+
if is_json_path(token):
|
|
138
|
+
reads_json = True
|
|
139
|
+
index += 1
|
|
140
|
+
return tool, args, reads_json
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def flags_of(args):
|
|
144
|
+
return [a for a in args if a.startswith("-")]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def is_violation(tool, args, has_json_input):
|
|
148
|
+
if not has_json_input:
|
|
149
|
+
return False
|
|
150
|
+
flags = flags_of(args)
|
|
151
|
+
if tool in ("grep", "egrep", "fgrep"):
|
|
152
|
+
if any(f in ("-l", "-L", "--files-with-matches", "--files-without-match") for f in flags):
|
|
153
|
+
return False
|
|
154
|
+
return any(f == "-o" or f == "--only-matching" or ("o" in f[1:] and not f.startswith("--")) for f in flags)
|
|
155
|
+
if tool in ("sed", "gsed"):
|
|
156
|
+
if any(f.startswith("-i") or f == "--in-place" for f in flags):
|
|
157
|
+
return True
|
|
158
|
+
return any(re.match(r"^-?\d*s[/|#]", a) or a.startswith("s/") or a.startswith("s|") for a in args)
|
|
159
|
+
if tool == "cut":
|
|
160
|
+
for i, arg in enumerate(args):
|
|
161
|
+
if arg == "-d" and i + 1 < len(args) and args[i + 1] in ('"', "'", ":", ","):
|
|
162
|
+
return True
|
|
163
|
+
if arg.startswith("-d") and len(arg) > 2 and arg[2:] in ('"', "'", ":", ","):
|
|
164
|
+
return True
|
|
165
|
+
return False
|
|
166
|
+
if tool in ("awk", "gawk"):
|
|
167
|
+
if any(f == "-F" or f.startswith("-F") for f in flags):
|
|
168
|
+
return True
|
|
169
|
+
return any(re.search(r"\$\d", a) for a in args if not a.startswith("-"))
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
tokens = tokenize(strip_heredoc_bodies(command))
|
|
174
|
+
if tokens is None:
|
|
175
|
+
sys.exit(0)
|
|
176
|
+
|
|
177
|
+
pipelines = split_pipelines(tokens)
|
|
178
|
+
|
|
179
|
+
# Whole-command jq exemption: any jq stage means the author is already using
|
|
180
|
+
# the right tool; mixed pipelines (jq | grep) are legitimate.
|
|
181
|
+
for pipeline in pipelines:
|
|
182
|
+
for segment in pipeline:
|
|
183
|
+
tool, _args, _reads = segment_parts(segment)
|
|
184
|
+
if tool == "jq":
|
|
185
|
+
sys.exit(0)
|
|
186
|
+
|
|
187
|
+
for pipeline in pipelines:
|
|
188
|
+
json_stream = False
|
|
189
|
+
for segment in pipeline:
|
|
190
|
+
tool, args, reads_json = segment_parts(segment)
|
|
191
|
+
if tool is None:
|
|
192
|
+
continue
|
|
193
|
+
has_json_input = reads_json or json_stream
|
|
194
|
+
if tool in TEXT_TOOLS and is_violation(tool, args, has_json_input):
|
|
195
|
+
sys.exit(1)
|
|
196
|
+
# Streams originating from a .json file stay json-classified through
|
|
197
|
+
# pass-through text stages (cat x.json | grep … | cut …).
|
|
198
|
+
if reads_json or (json_stream and tool in TEXT_TOOLS | {"cat", "head", "tail", "sort", "uniq", "tr", "xargs"}):
|
|
199
|
+
json_stream = True
|
|
200
|
+
else:
|
|
201
|
+
json_stream = False
|
|
202
|
+
|
|
203
|
+
sys.exit(0)
|
|
204
|
+
PY
|
|
205
|
+
then
|
|
206
|
+
cat >&2 <<'EOF'
|
|
207
|
+
BLOCKED: this command parses JSON with text tools (grep/sed/cut/awk). Text
|
|
208
|
+
tools break on valid JSON — multiline values, escaped quotes, reordered keys,
|
|
209
|
+
nested objects — producing silently wrong output instead of errors. Use jq for
|
|
210
|
+
all structural JSON reads and writes in shell. Typical fixes:
|
|
211
|
+
read a field: jq -r '.field' file.json
|
|
212
|
+
filter items: jq '.items[] | select(.name=="x")' file.json
|
|
213
|
+
edit in place: jq '.key="value"' file.json > tmp && mv tmp file.json
|
|
214
|
+
If you are only SEARCHING text inside a JSON file (not extracting values), use
|
|
215
|
+
`rg <pattern> file.json` with no extraction pipe and this hook will not fire.
|
|
216
|
+
EOF
|
|
217
|
+
exit 2
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
exit 0
|
|
@@ -50,16 +50,28 @@ EOF
|
|
|
50
50
|
# text from the destructive-command scans below. Unknown executable heredocs
|
|
51
51
|
# remain visible to every built-in and custom rule. Ambiguous or malformed
|
|
52
52
|
# heredocs fail closed instead of guessing which text the shell would execute.
|
|
53
|
+
#
|
|
54
|
+
# block_heredoc() teaches the remediation the moment the wall is hit: heredoc
|
|
55
|
+
# denials overwhelmingly come from `git commit -m "$(cat <<EOF …)"` attempts,
|
|
56
|
+
# and a bare denial strands the agent with no path forward (gardener #1789).
|
|
57
|
+
block_heredoc() {
|
|
58
|
+
block "$1
|
|
59
|
+
Heredoc commit invocations are blocked (the payload is executable shell).
|
|
60
|
+
Fix: write the commit message to a file and run \`git commit -F <file>\`.
|
|
61
|
+
Every commit must also carry a Co-authored-by trailer for a supported agent
|
|
62
|
+
(Claude/Codex/OpenCode) — the commit-msg hook enforces this."
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
command_for_guards="$command_str"
|
|
54
66
|
case "$command_str" in
|
|
55
67
|
*'<<'*)
|
|
56
68
|
hook_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
57
69
|
heredoc_parser="$hook_dir/parity-safety-net-heredoc.py"
|
|
58
70
|
if ! command -v python3 >/dev/null 2>&1 || [ ! -r "$heredoc_parser" ]; then
|
|
59
|
-
|
|
71
|
+
block_heredoc "cannot safely classify heredoc command because its parser runtime is unavailable"
|
|
60
72
|
fi
|
|
61
73
|
if ! printf '%s\n' "$command_str" | /bin/bash -n >/dev/null 2>&1; then
|
|
62
|
-
|
|
74
|
+
block_heredoc "malformed heredoc command failed shell syntax validation"
|
|
63
75
|
fi
|
|
64
76
|
|
|
65
77
|
parser_status=0
|
|
@@ -72,8 +84,8 @@ case "$command_str" in
|
|
|
72
84
|
case "$parser_status" in
|
|
73
85
|
0) command_for_guards="$parser_output" ;;
|
|
74
86
|
10) command_for_guards="$command_str" ;;
|
|
75
|
-
20)
|
|
76
|
-
*)
|
|
87
|
+
20) block_heredoc "malformed or ambiguous heredoc command cannot be safely classified" ;;
|
|
88
|
+
*) block_heredoc "heredoc parser failed; command was denied fail-closed" ;;
|
|
77
89
|
esac
|
|
78
90
|
;;
|
|
79
91
|
esac
|
|
@@ -226,6 +226,14 @@ const AGY_PLUGIN_HOOKS = [
|
|
|
226
226
|
agyScript: "parity-safety-net.agy.sh",
|
|
227
227
|
supportScripts: ["parity-safety-net.sh", "parity-safety-net-heredoc.py"],
|
|
228
228
|
},
|
|
229
|
+
{
|
|
230
|
+
sourceScript: "block-shell-json-parsing.sh",
|
|
231
|
+
hookName: "lisa-block-shell-json-parsing",
|
|
232
|
+
event: "PreToolUse",
|
|
233
|
+
matcher: "run_command",
|
|
234
|
+
agyScript: "block-shell-json-parsing.agy.sh",
|
|
235
|
+
supportScripts: ["block-shell-json-parsing.sh"],
|
|
236
|
+
},
|
|
229
237
|
];
|
|
230
238
|
|
|
231
239
|
/**
|
|
@@ -43,6 +43,13 @@ const SCRIPT_RULES = {
|
|
|
43
43
|
agy: true,
|
|
44
44
|
copilot: true,
|
|
45
45
|
},
|
|
46
|
+
"block-shell-json-parsing.sh": {
|
|
47
|
+
claude: true,
|
|
48
|
+
codex: true,
|
|
49
|
+
cursor: true,
|
|
50
|
+
agy: true, // ships via AGY_PLUGIN_HOOKS (agy-protocol adapter), like the other PreToolUse guards
|
|
51
|
+
copilot: true,
|
|
52
|
+
},
|
|
46
53
|
"enforce-team-first.sh": {
|
|
47
54
|
claude: true,
|
|
48
55
|
codex: false,
|