@windyroad/risk-scorer 0.17.4 → 0.18.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/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +13 -2
- package/bin/install.mjs +42 -8
- package/hooks/codex-agent-completion.mjs +82 -0
- package/hooks/hooks.json +5 -2
- package/hooks/risk-scorer-dispatch.sh +3 -0
- package/hooks/risk-scorer-scaffold-nudge.sh +15 -4
- package/package.json +5 -2
- package/scripts/codex-agents.mjs +208 -0
- package/scripts/sync-codex-skills.mjs +8 -3
- package/skills/assess-external-comms/SKILL.md +5 -4
- package/skills/assess-inbound-report/SKILL.md +5 -4
- package/skills/assess-release/SKILL.md +5 -4
- package/skills/assess-wip/SKILL.md +5 -4
- package/skills/bootstrap-catalog/SKILL.md +5 -4
- package/skills/create-risk/SKILL.md +5 -4
- package/skills/external-comms/SKILL.md +5 -4
- package/skills/pipeline/SKILL.md +5 -4
- package/skills/update-policy/SKILL.md +6 -5
- package/skills/wip/SKILL.md +5 -4
- package/agents/test/inbound-report-contract.bats +0 -225
- package/agents/test/risk-scorer-above-appetite-stop.bats +0 -80
- package/agents/test/risk-scorer-catalog-consumption.bats +0 -138
- package/agents/test/risk-scorer-commit-verdict.bats +0 -66
- package/agents/test/risk-scorer-monitoring-not-a-control.bats +0 -76
- package/agents/test/risk-scorer-reducing-bypass-criteria.bats +0 -62
- package/agents/test/risk-scorer-register-hint.bats +0 -111
- package/agents/test/risk-scorer-structured-remediations.bats +0 -123
- package/agents/test/risk-scorer-user-stated-preconditions.bats +0 -89
- package/hooks/test/ci-status-gate.bats +0 -234
- package/hooks/test/external-comms-gate.bats +0 -528
- package/hooks/test/external-comms-key-substance.bats +0 -74
- package/hooks/test/git-push-gate.bats +0 -82
- package/hooks/test/pipeline-state-changeset-partition.bats +0 -181
- package/hooks/test/pipeline-state-hash.bats +0 -195
- package/hooks/test/plan-risk-guidance-once-per-session.bats +0 -95
- package/hooks/test/reducing-marker-persistence.bats +0 -236
- package/hooks/test/risk-gate.bats +0 -330
- package/hooks/test/risk-policy-project-root.bats +0 -20
- package/hooks/test/risk-score-commit-gate-adr-031-bypass.bats +0 -98
- package/hooks/test/risk-score-commit-gate-cadence-staleness.bats +0 -105
- package/hooks/test/risk-score-mark-external-comms-prompt-parse.bats +0 -94
- package/hooks/test/risk-score-mark-register-queue.bats +0 -253
- package/hooks/test/risk-score-mark.bats +0 -112
- package/hooks/test/risk-scorer-hook-dispatch.bats +0 -77
- package/hooks/test/risk-scorer-no-stop-hook.bats +0 -19
- package/hooks/test/risk-scorer-scaffold-nudge.bats +0 -132
- package/hooks/test/slide-marker-on-subprocess-return.bats +0 -118
- package/hooks/test/wip-risk-mark.bats +0 -64
- package/scripts/test/drain-register-queue.bats +0 -359
- package/scripts/test/evaluate-graduation.bats +0 -533
- package/scripts/test/extract-risks-from-reports.bats +0 -366
- package/scripts/test/restage-commit.bats +0 -137
|
@@ -1,528 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
# Tests for external-comms-gate.sh (P064 / ADR-028 amended).
|
|
3
|
-
# Behavioural: the gate denies outbound prose tool calls until the
|
|
4
|
-
# wr-risk-scorer:external-comms subagent has reviewed the draft and a
|
|
5
|
-
# per-evaluator marker has been written. Hard-fail leak patterns deny
|
|
6
|
-
# immediately without delegation.
|
|
7
|
-
#
|
|
8
|
-
# Note: secret-shaped strings are constructed at runtime via concatenation
|
|
9
|
-
# so this test file itself does not trip the secret-leak-gate hook.
|
|
10
|
-
|
|
11
|
-
setup() {
|
|
12
|
-
HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
13
|
-
HOOK="$HOOKS_DIR/external-comms-gate.sh"
|
|
14
|
-
|
|
15
|
-
TEST_SESSION="bats-extcomms-gate-$$-${BATS_TEST_NUMBER}"
|
|
16
|
-
RDIR="${TMPDIR:-/tmp}/claude-risk-${TEST_SESSION}"
|
|
17
|
-
rm -rf "$RDIR"
|
|
18
|
-
mkdir -p "$RDIR"
|
|
19
|
-
|
|
20
|
-
# Default: assume RISK-POLICY.md is present in repo root (it is).
|
|
21
|
-
TEST_PROJECT_DIR="$(mktemp -d)"
|
|
22
|
-
printf "## Confidential Information\n- Client names\n- Revenue figures\n" \
|
|
23
|
-
> "$TEST_PROJECT_DIR/RISK-POLICY.md"
|
|
24
|
-
|
|
25
|
-
unset BYPASS_RISK_GATE
|
|
26
|
-
|
|
27
|
-
# Construct secret-shaped strings at runtime to avoid tripping the
|
|
28
|
-
# repo's own secret-leak-gate when this file is committed/edited.
|
|
29
|
-
GH_TOKEN_LIKE="g""hp""_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
30
|
-
AWS_KEY_LIKE="A""KIA""IOSFODNN7EXAMPLE"
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
teardown() {
|
|
34
|
-
rm -rf "$RDIR"
|
|
35
|
-
rm -rf "$TEST_PROJECT_DIR"
|
|
36
|
-
unset BYPASS_RISK_GATE
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
# ---------- Helpers ----------
|
|
40
|
-
|
|
41
|
-
# Build a PreToolUse:Bash input for a given command via python so quoting is safe.
|
|
42
|
-
build_bash_input() {
|
|
43
|
-
local cmd="$1"
|
|
44
|
-
python3 -c "
|
|
45
|
-
import json, sys
|
|
46
|
-
print(json.dumps({
|
|
47
|
-
'session_id': '$TEST_SESSION',
|
|
48
|
-
'tool_name': 'Bash',
|
|
49
|
-
'tool_input': {'command': sys.argv[1]},
|
|
50
|
-
}))
|
|
51
|
-
" "$cmd"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# Build a PreToolUse:Write input for a changeset path with body content.
|
|
55
|
-
build_write_input() {
|
|
56
|
-
local file_path="$1"
|
|
57
|
-
local content="$2"
|
|
58
|
-
python3 -c "
|
|
59
|
-
import json, sys
|
|
60
|
-
print(json.dumps({
|
|
61
|
-
'session_id': '$TEST_SESSION',
|
|
62
|
-
'tool_name': 'Write',
|
|
63
|
-
'tool_input': {'file_path': sys.argv[1], 'content': sys.argv[2]},
|
|
64
|
-
}))
|
|
65
|
-
" "$file_path" "$content"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
# Mock `gh repo view --json visibility` for the git-commit-message surface
|
|
69
|
-
# repo-visibility precondition (P365). vis ∈ {PUBLIC,PRIVATE,INTERNAL}; pass the
|
|
70
|
-
# literal "FAIL" to simulate gh absent / unauthenticated (non-zero exit). The
|
|
71
|
-
# mock ignores args and prints the chosen visibility so the hook's
|
|
72
|
-
# `gh repo view --json visibility -q .visibility` resolves deterministically.
|
|
73
|
-
mock_gh_visibility() {
|
|
74
|
-
local vis="$1"
|
|
75
|
-
mkdir -p "$TEST_PROJECT_DIR/mockbin"
|
|
76
|
-
if [ "$vis" = "FAIL" ]; then
|
|
77
|
-
printf '#!/usr/bin/env bash\nexit 1\n' > "$TEST_PROJECT_DIR/mockbin/gh"
|
|
78
|
-
else
|
|
79
|
-
printf '#!/usr/bin/env bash\necho %s\n' "$vis" > "$TEST_PROJECT_DIR/mockbin/gh"
|
|
80
|
-
fi
|
|
81
|
-
chmod +x "$TEST_PROJECT_DIR/mockbin/gh"
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# Run the hook in a project dir with RISK-POLICY.md present, piping JSON via stdin.
|
|
85
|
-
# mockbin (if populated by mock_gh_visibility) is prepended to PATH so the
|
|
86
|
-
# git-commit-message visibility precondition resolves against the mock.
|
|
87
|
-
run_hook() {
|
|
88
|
-
local input="$1"
|
|
89
|
-
run bash -c "cd '$TEST_PROJECT_DIR' && export PATH='$TEST_PROJECT_DIR/mockbin':\$PATH && printf '%s' \"\$1\" | '$HOOK'" _ "$input"
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
# ---------- Tests ----------
|
|
93
|
-
|
|
94
|
-
@test "non-matching Bash command (ls) is allowed silently" {
|
|
95
|
-
INPUT=$(build_bash_input "ls -la")
|
|
96
|
-
run_hook "$INPUT"
|
|
97
|
-
[ "$status" -eq 0 ]
|
|
98
|
-
[ -z "$output" ]
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
@test "gh issue create with clean draft denies and prompts external-comms delegation (no marker yet)" {
|
|
102
|
-
INPUT=$(build_bash_input "gh issue create --title T --body 'we observed a build failure on Node 20'")
|
|
103
|
-
run_hook "$INPUT"
|
|
104
|
-
[ "$status" -eq 0 ]
|
|
105
|
-
[[ "$output" == *"permissionDecision"* ]]
|
|
106
|
-
[[ "$output" == *"deny"* ]]
|
|
107
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# P377/RFC-029: the BYPASS_RISK_GATE env override was removed. The only
|
|
111
|
-
# clearance path named in the deny is delegation to the external-comms subagent.
|
|
112
|
-
@test "marker-absent deny names the reviewer and offers no env override (P377/RFC-029)" {
|
|
113
|
-
INPUT=$(build_bash_input "gh issue create --title T --body 'we observed a build failure on Node 20'")
|
|
114
|
-
run_hook "$INPUT"
|
|
115
|
-
[ "$status" -eq 0 ]
|
|
116
|
-
[[ "$output" == *"deny"* ]]
|
|
117
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
118
|
-
[[ "$output" != *"BYPASS_RISK_GATE=1"* ]]
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
@test "hard-fail credential pattern (GitHub token) denies immediately with leak reason" {
|
|
122
|
-
INPUT=$(build_bash_input "gh issue comment 42 --body 'token=${GH_TOKEN_LIKE}'")
|
|
123
|
-
run_hook "$INPUT"
|
|
124
|
-
[ "$status" -eq 0 ]
|
|
125
|
-
[[ "$output" == *"permissionDecision"* ]]
|
|
126
|
-
[[ "$output" == *"deny"* ]]
|
|
127
|
-
[[ "$output" == *"GitHub token"* ]] || [[ "$output" == *"credential"* ]]
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
@test "hard-fail credential pattern (AWS key) denies immediately" {
|
|
131
|
-
INPUT=$(build_bash_input "gh pr create --title T --body 'AWS=${AWS_KEY_LIKE}'")
|
|
132
|
-
run_hook "$INPUT"
|
|
133
|
-
[ "$status" -eq 0 ]
|
|
134
|
-
[[ "$output" == *"deny"* ]]
|
|
135
|
-
[[ "$output" == *"AWS"* ]] || [[ "$output" == *"credential"* ]]
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@test "BYPASS_RISK_GATE=1 does NOT bypass the gate (removed P377/RFC-029)" {
|
|
139
|
-
INPUT=$(build_bash_input "gh issue create --title T --body 'we observed a build failure'")
|
|
140
|
-
run bash -c "cd '$TEST_PROJECT_DIR' && BYPASS_RISK_GATE=1 printf '%s' \"\$1\" | BYPASS_RISK_GATE=1 '$HOOK'" _ "$INPUT"
|
|
141
|
-
[ "$status" -eq 0 ]
|
|
142
|
-
# The env override no longer short-circuits — the gate still denies the
|
|
143
|
-
# unreviewed external-comms draft.
|
|
144
|
-
[[ "$output" == *"deny"* ]]
|
|
145
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@test "per-evaluator marker (external-comms-risk-reviewed-<KEY>) allows the call (ADR-028 amended 2026-05-14)" {
|
|
149
|
-
DRAFT="we observed a build failure on Node 20"
|
|
150
|
-
SURFACE="gh-issue-create"
|
|
151
|
-
KEY=$(printf '%s\n%s' "$DRAFT" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
152
|
-
touch "${RDIR}/external-comms-risk-reviewed-${KEY}"
|
|
153
|
-
|
|
154
|
-
INPUT=$(build_bash_input "gh issue create --title T --body '$DRAFT'")
|
|
155
|
-
run_hook "$INPUT"
|
|
156
|
-
[ "$status" -eq 0 ]
|
|
157
|
-
[ -z "$output" ]
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
@test "legacy combined marker (external-comms-reviewed-<KEY>) does NOT satisfy the risk gate (P038 per-evaluator scheme)" {
|
|
161
|
-
DRAFT="we observed a build failure on Node 20"
|
|
162
|
-
SURFACE="gh-issue-create"
|
|
163
|
-
KEY=$(printf '%s\n%s' "$DRAFT" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
164
|
-
# Pre-amendment combined marker — should NOT satisfy the new per-evaluator gate.
|
|
165
|
-
touch "${RDIR}/external-comms-reviewed-${KEY}"
|
|
166
|
-
|
|
167
|
-
INPUT=$(build_bash_input "gh issue create --title T --body '$DRAFT'")
|
|
168
|
-
run_hook "$INPUT"
|
|
169
|
-
[ "$status" -eq 0 ]
|
|
170
|
-
[[ "$output" == *"deny"* ]]
|
|
171
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
@test "RISK-POLICY.md absent yields advisory-only mode (permits)" {
|
|
175
|
-
rm -f "$TEST_PROJECT_DIR/RISK-POLICY.md"
|
|
176
|
-
INPUT=$(build_bash_input "gh issue create --title T --body 'we observed a failure'")
|
|
177
|
-
run_hook "$INPUT"
|
|
178
|
-
[ "$status" -eq 0 ]
|
|
179
|
-
# Must NOT deny when policy file is absent.
|
|
180
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
181
|
-
[[ "$output" != *"\"permissionDecision\":\"deny\""* ]]
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
@test "PreToolUse:Write on .changeset/*.md with revenue leak content denies" {
|
|
185
|
-
INPUT=$(build_write_input ".changeset/wr-risk-scorer-extcomms.md" "Add gate; covers Acme Corp \$2.4M ARR client.")
|
|
186
|
-
run_hook "$INPUT"
|
|
187
|
-
[ "$status" -eq 0 ]
|
|
188
|
-
[[ "$output" == *"deny"* ]]
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
@test "PreToolUse:Write on .changeset/*.md with clean content denies-then-delegates (no marker yet)" {
|
|
192
|
-
INPUT=$(build_write_input ".changeset/wr-risk-scorer-extcomms.md" "Add external-comms gate covering gh issue and pr surfaces.")
|
|
193
|
-
run_hook "$INPUT"
|
|
194
|
-
[ "$status" -eq 0 ]
|
|
195
|
-
[[ "$output" == *"deny"* ]]
|
|
196
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
@test "PreToolUse:Write on a non-changeset path is ignored" {
|
|
200
|
-
INPUT=$(build_write_input "src/foo.ts" "Acme Corp client revenue \$2.4M")
|
|
201
|
-
run_hook "$INPUT"
|
|
202
|
-
[ "$status" -eq 0 ]
|
|
203
|
-
[ -z "$output" ]
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
@test "gh api security-advisories triggers the gate" {
|
|
207
|
-
INPUT=$(build_bash_input "gh api repos/foo/bar/security-advisories --method POST --field summary='leak via X'")
|
|
208
|
-
run_hook "$INPUT"
|
|
209
|
-
[ "$status" -eq 0 ]
|
|
210
|
-
[[ "$output" == *"deny"* ]]
|
|
211
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
# ---------------------------------------------------------------------------
|
|
215
|
-
# P405 — read-only `gh api` polls must NOT trip the gate. `gh api` defaults to
|
|
216
|
-
# GET; it only carries an outbound prose body (the thing this gate reviews)
|
|
217
|
-
# when a request-body flag (-f/--field, -F/--raw-field, --input) is present.
|
|
218
|
-
# The `/wr-itil:review-problems` Step 4.5c security-advisories discovery poll
|
|
219
|
-
# is read-only (`--jq` filter, no body flag) and was being denied as if it
|
|
220
|
-
# were an advisory draft, silently blacking out one discovery channel.
|
|
221
|
-
# ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
@test "P405: read-only gh api security-advisories poll (--jq, no body flag) is not gated" {
|
|
224
|
-
INPUT=$(build_bash_input "gh api repos/foo/bar/security-advisories --jq '.[].ghsa_id'")
|
|
225
|
-
run_hook "$INPUT"
|
|
226
|
-
[ "$status" -eq 0 ]
|
|
227
|
-
[ -z "$output" ]
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
@test "P405: bare gh api security-advisories read (no flags) is not gated" {
|
|
231
|
-
INPUT=$(build_bash_input "gh api repos/foo/bar/security-advisories")
|
|
232
|
-
run_hook "$INPUT"
|
|
233
|
-
[ "$status" -eq 0 ]
|
|
234
|
-
[ -z "$output" ]
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
@test "P405: read-only gh api comments poll (--jq, no body flag) is not gated" {
|
|
238
|
-
INPUT=$(build_bash_input "gh api repos/foo/bar/issues/1/comments --jq '.[].body'")
|
|
239
|
-
run_hook "$INPUT"
|
|
240
|
-
[ "$status" -eq 0 ]
|
|
241
|
-
[ -z "$output" ]
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
@test "P405: gh api security-advisories write with -f body flag still triggers the gate" {
|
|
245
|
-
INPUT=$(build_bash_input "gh api repos/foo/bar/security-advisories -f summary='new advisory'")
|
|
246
|
-
run_hook "$INPUT"
|
|
247
|
-
[ "$status" -eq 0 ]
|
|
248
|
-
[[ "$output" == *"deny"* ]]
|
|
249
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
@test "npm publish triggers the gate" {
|
|
253
|
-
INPUT=$(build_bash_input "npm publish")
|
|
254
|
-
run_hook "$INPUT"
|
|
255
|
-
[ "$status" -eq 0 ]
|
|
256
|
-
[[ "$output" == *"deny"* ]]
|
|
257
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
# ---------------------------------------------------------------------------
|
|
261
|
-
# P010 / ADR-028 amended 2026-05-25 — deny-after-PASS regression.
|
|
262
|
-
# The gate sees the FULL Write content (YAML frontmatter + body) on the
|
|
263
|
-
# changeset-author surface, but the mark hook keys the marker on the body
|
|
264
|
-
# the agent wrapped in <draft>. Before the fix the gate hashed the full
|
|
265
|
-
# content (incl. frontmatter), so the body-keyed PASS marker landed at a
|
|
266
|
-
# key the gate never re-read → permanent deny-after-PASS. After the fix the
|
|
267
|
-
# gate strips frontmatter before hashing, so a body-keyed marker permits.
|
|
268
|
-
# ---------------------------------------------------------------------------
|
|
269
|
-
|
|
270
|
-
@test "P010: changeset Write permits when the PASS marker is keyed on the <draft> body (frontmatter stripped before hash)" {
|
|
271
|
-
BODY="external-comms gate strips changeset frontmatter before key hash"
|
|
272
|
-
SURFACE="changeset-author"
|
|
273
|
-
# Marker keyed on the body the mark-hook helper derives from <draft> — for a
|
|
274
|
-
# frontmatter-free body the canonical key equals the raw printf-of-body key.
|
|
275
|
-
KEY=$(printf '%s\n%s' "$BODY" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
276
|
-
touch "${RDIR}/external-comms-risk-reviewed-${KEY}"
|
|
277
|
-
|
|
278
|
-
# The gate sees the full changeset file: frontmatter + blank line + body.
|
|
279
|
-
CONTENT=$'---\n"@windyroad/risk-scorer": patch\n---\n\n'"$BODY"
|
|
280
|
-
INPUT=$(build_write_input ".changeset/p010-fix.md" "$CONTENT")
|
|
281
|
-
run_hook "$INPUT"
|
|
282
|
-
[ "$status" -eq 0 ]
|
|
283
|
-
[ -z "$output" ]
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
# ---------------------------------------------------------------------------
|
|
287
|
-
# P082 Phase 1 — git commit message surface (risk evaluator).
|
|
288
|
-
# Commit messages reach git log / PR commits tab / release notes /
|
|
289
|
-
# CHANGELOG. The risk evaluator gates the body for leak patterns
|
|
290
|
-
# (credentials, prod URLs, business-context-paired financials/user counts)
|
|
291
|
-
# AND defers structured leak-free drafts to the wr-risk-scorer:external-comms
|
|
292
|
-
# subagent. Editor flow (bare `git commit`) is out of scope per P082 SC1.
|
|
293
|
-
# ---------------------------------------------------------------------------
|
|
294
|
-
|
|
295
|
-
@test "P082: git commit -m with leak-shaped credential body denies via leak pre-filter" {
|
|
296
|
-
INPUT=$(build_bash_input "git commit -m \"docs: token=${GH_TOKEN_LIKE}\"")
|
|
297
|
-
run_hook "$INPUT"
|
|
298
|
-
[ "$status" -eq 0 ]
|
|
299
|
-
[[ "$output" == *"deny"* ]]
|
|
300
|
-
[[ "$output" == *"git-commit-message"* ]]
|
|
301
|
-
[[ "$output" == *"GitHub token"* ]] || [[ "$output" == *"credential"* ]]
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
@test "P082: git commit -m with leak-free body denies and delegates to risk evaluator" {
|
|
305
|
-
mock_gh_visibility PUBLIC
|
|
306
|
-
INPUT=$(build_bash_input "git commit -m \"fix(foo): handle null input\"")
|
|
307
|
-
run_hook "$INPUT"
|
|
308
|
-
[ "$status" -eq 0 ]
|
|
309
|
-
[[ "$output" == *"deny"* ]]
|
|
310
|
-
[[ "$output" == *"git-commit-message"* ]]
|
|
311
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
@test "P082: git commit --amend -m is intercepted (P082 SC2)" {
|
|
315
|
-
mock_gh_visibility PUBLIC
|
|
316
|
-
INPUT=$(build_bash_input "git commit --amend -m \"rewritten subject\"")
|
|
317
|
-
run_hook "$INPUT"
|
|
318
|
-
[ "$status" -eq 0 ]
|
|
319
|
-
[[ "$output" == *"deny"* ]]
|
|
320
|
-
[[ "$output" == *"git-commit-message"* ]]
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
@test "P082: git commit HEREDOC body is intercepted and the body becomes the marker key" {
|
|
324
|
-
# Build a HEREDOC-shaped command. The hook regex pulls the body BETWEEN
|
|
325
|
-
# the <<'EOF' opener and the closing EOF marker — the extracted DRAFT is
|
|
326
|
-
# the inner text, NOT the literal `$(cat <<'EOF' ... EOF)` wrapper.
|
|
327
|
-
mock_gh_visibility PUBLIC
|
|
328
|
-
BODY=$'feat(foo): add bar\n\nWe observed a build failure on Node 20.'
|
|
329
|
-
CMD=$'git commit -m "$(cat <<\'EOF\'\n'"$BODY"$'\nEOF\n)"'
|
|
330
|
-
INPUT=$(build_bash_input "$CMD")
|
|
331
|
-
run_hook "$INPUT"
|
|
332
|
-
[ "$status" -eq 0 ]
|
|
333
|
-
[[ "$output" == *"deny"* ]]
|
|
334
|
-
[[ "$output" == *"git-commit-message"* ]]
|
|
335
|
-
|
|
336
|
-
# Pre-place the per-evaluator marker keyed on the extracted HEREDOC body
|
|
337
|
-
# + the git-commit-message surface; the second run must permit silently.
|
|
338
|
-
SURFACE="git-commit-message"
|
|
339
|
-
KEY=$(printf '%s\n%s' "$BODY" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
340
|
-
touch "${RDIR}/external-comms-risk-reviewed-${KEY}"
|
|
341
|
-
run_hook "$INPUT"
|
|
342
|
-
[ "$status" -eq 0 ]
|
|
343
|
-
[ -z "$output" ]
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
@test "P082: bare git commit (editor flow) is silently allowed per SC1" {
|
|
347
|
-
# No -m / --message → .git/COMMIT_EDITMSG doesn't exist at PreToolUse
|
|
348
|
-
# time. Phase 1 skip is pragmatic; the editor flow has user-eyeballs.
|
|
349
|
-
INPUT=$(build_bash_input "git commit")
|
|
350
|
-
run_hook "$INPUT"
|
|
351
|
-
[ "$status" -eq 0 ]
|
|
352
|
-
[ -z "$output" ]
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
@test "P082: git merge is silently allowed (not a git commit verb, SC3)" {
|
|
356
|
-
INPUT=$(build_bash_input "git merge --no-ff feature-branch")
|
|
357
|
-
run_hook "$INPUT"
|
|
358
|
-
[ "$status" -eq 0 ]
|
|
359
|
-
[ -z "$output" ]
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
@test "P082→P377/RFC-029: BYPASS_RISK_GATE=1 is inert — does not short-circuit the gate" {
|
|
363
|
-
# gh-issue is an unconditionally-gated external surface (no visibility
|
|
364
|
-
# precondition). Setting BYPASS_RISK_GATE=1 has no effect: the unreviewed
|
|
365
|
-
# draft still denies, identically to the no-env-var case.
|
|
366
|
-
INPUT=$(build_bash_input "gh issue create --title T --body 'we observed a build failure on Node 20'")
|
|
367
|
-
run bash -c "cd '$TEST_PROJECT_DIR' && BYPASS_RISK_GATE=1 printf '%s' \"\$1\" | BYPASS_RISK_GATE=1 '$HOOK'" _ "$INPUT"
|
|
368
|
-
[ "$status" -eq 0 ]
|
|
369
|
-
[[ "$output" == *"deny"* ]]
|
|
370
|
-
[[ "$output" == *"wr-risk-scorer:external-comms"* ]]
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
@test "P082: per-evaluator marker keyed on (body, git-commit-message) permits the call" {
|
|
374
|
-
mock_gh_visibility PUBLIC
|
|
375
|
-
BODY="docs(retro): close iter 3 ask-hygiene trail"
|
|
376
|
-
SURFACE="git-commit-message"
|
|
377
|
-
KEY=$(printf '%s\n%s' "$BODY" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
378
|
-
touch "${RDIR}/external-comms-risk-reviewed-${KEY}"
|
|
379
|
-
|
|
380
|
-
INPUT=$(build_bash_input "git commit -m \"$BODY\"")
|
|
381
|
-
run_hook "$INPUT"
|
|
382
|
-
[ "$status" -eq 0 ]
|
|
383
|
-
[ -z "$output" ]
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
# ---------------------------------------------------------------------------
|
|
387
|
-
# P365 — repo-visibility precondition on the git-commit-message surface.
|
|
388
|
-
# A commit message is external-facing prose ONLY when it lands in a PUBLIC
|
|
389
|
-
# GitHub repo (git log / PR commits tab / release notes / CHANGELOG). In
|
|
390
|
-
# private/internal repos — or any repo whose visibility cannot be confirmed
|
|
391
|
-
# PUBLIC — the marker-review delegation deny is a pure false-positive
|
|
392
|
-
# (user direction 2026-06-11: "this MUST NOT fire for private repos"). The
|
|
393
|
-
# precondition silent-passes the marker gate for the git-commit-message
|
|
394
|
-
# surface on any non-PUBLIC / indeterminate gh result. It is scoped to that
|
|
395
|
-
# surface only and runs AFTER the leak pre-filter, so the credential/prod-URL
|
|
396
|
-
# security net survives the short-circuit.
|
|
397
|
-
# ---------------------------------------------------------------------------
|
|
398
|
-
|
|
399
|
-
@test "P365: git commit -m in a PRIVATE repo silent-passes (no external-comms deny)" {
|
|
400
|
-
mock_gh_visibility PRIVATE
|
|
401
|
-
INPUT=$(build_bash_input "git commit -m \"fix(foo): handle null input\"")
|
|
402
|
-
run_hook "$INPUT"
|
|
403
|
-
[ "$status" -eq 0 ]
|
|
404
|
-
[ -z "$output" ]
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
@test "P365: git commit -m in an INTERNAL repo silent-passes" {
|
|
408
|
-
mock_gh_visibility INTERNAL
|
|
409
|
-
INPUT=$(build_bash_input "git commit -m \"fix(foo): handle null input\"")
|
|
410
|
-
run_hook "$INPUT"
|
|
411
|
-
[ "$status" -eq 0 ]
|
|
412
|
-
[ -z "$output" ]
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
@test "P365: git commit -m when gh is unavailable/indeterminate silent-passes (fail-non-public)" {
|
|
416
|
-
mock_gh_visibility FAIL
|
|
417
|
-
INPUT=$(build_bash_input "git commit -m \"fix(foo): handle null input\"")
|
|
418
|
-
run_hook "$INPUT"
|
|
419
|
-
[ "$status" -eq 0 ]
|
|
420
|
-
[ -z "$output" ]
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
@test "P365: git commit -m in a PUBLIC repo still denies+delegates (gate intact, precondition surface-scoped)" {
|
|
424
|
-
mock_gh_visibility PUBLIC
|
|
425
|
-
INPUT=$(build_bash_input "git commit -m \"fix(foo): handle null input\"")
|
|
426
|
-
run_hook "$INPUT"
|
|
427
|
-
[ "$status" -eq 0 ]
|
|
428
|
-
[[ "$output" == *"deny"* ]]
|
|
429
|
-
[[ "$output" == *"git-commit-message"* ]]
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
@test "P360: empty EXTERNAL_COMMS_SKIP_SURFACES leaves the risk evaluator gating commit messages (divergence guard)" {
|
|
433
|
-
# The voice-tone evaluator skips git-commit-message (its policy disclaims it);
|
|
434
|
-
# the risk evaluator's .conf leaves EXTERNAL_COMMS_SKIP_SURFACES empty, so its
|
|
435
|
-
# leak check on commit bodies stays meaningful. Guards against a regression
|
|
436
|
-
# that accidentally skips this surface for risk-scorer (e.g. defaulting the
|
|
437
|
-
# knob non-empty, or syncing voice-tone's value into the shared gate body).
|
|
438
|
-
grep -qE '^EXTERNAL_COMMS_SKIP_SURFACES=$' "$HOOKS_DIR/external-comms-evaluator.conf"
|
|
439
|
-
mock_gh_visibility PUBLIC
|
|
440
|
-
INPUT=$(build_bash_input "git commit -m \"fix(foo): handle null input\"")
|
|
441
|
-
run_hook "$INPUT"
|
|
442
|
-
[ "$status" -eq 0 ]
|
|
443
|
-
[[ "$output" == *"deny"* ]]
|
|
444
|
-
[[ "$output" == *"git-commit-message"* ]]
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
@test "P365: leak-shaped credential in a PRIVATE-repo commit body still hard-fails (security net survives)" {
|
|
448
|
-
mock_gh_visibility PRIVATE
|
|
449
|
-
INPUT=$(build_bash_input "git commit -m \"docs: token=${GH_TOKEN_LIKE}\"")
|
|
450
|
-
run_hook "$INPUT"
|
|
451
|
-
[ "$status" -eq 0 ]
|
|
452
|
-
[[ "$output" == *"deny"* ]]
|
|
453
|
-
[[ "$output" == *"git-commit-message"* ]]
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
@test "P365: PRIVATE visibility does NOT short-circuit the gh-issue surface (still denies+delegates)" {
|
|
457
|
-
mock_gh_visibility PRIVATE
|
|
458
|
-
INPUT=$(build_bash_input "gh issue create --title x --body 'a clean issue body'")
|
|
459
|
-
run_hook "$INPUT"
|
|
460
|
-
[ "$status" -eq 0 ]
|
|
461
|
-
[[ "$output" == *"deny"* ]]
|
|
462
|
-
[[ "$output" == *"gh-issue-create"* ]]
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
# ---------------------------------------------------------------------------
|
|
466
|
-
# P364 — backtick-bearing double-quoted --body marker-key mismatch.
|
|
467
|
-
# When an outbound body contains markdown backticks (a code span), the
|
|
468
|
-
# orchestrator must backslash-escape them to survive bash double quotes:
|
|
469
|
-
# `gh issue comment 42 --body "Fixed in \`foo\` ..."`. Before the fix the
|
|
470
|
-
# gate extracted the RAW shell-escaped body (literal `\`` with backslash)
|
|
471
|
-
# from the command text, while the PostToolUse mark hook hashed the LOGICAL
|
|
472
|
-
# <draft> body (plain backticks). The two sha256 keys diverged → the PASS
|
|
473
|
-
# marker landed at a key the gate never re-read → permanent deny-after-PASS.
|
|
474
|
-
# After the fix the gate unescapes bash double-quote backslash-escapes on
|
|
475
|
-
# the captured double-quoted body so its DRAFT is byte-equal to the logical
|
|
476
|
-
# reviewed draft and the body-keyed marker permits. DISTINCT from P276 /
|
|
477
|
-
# P010 (whitespace / frontmatter) — this is a shell-escaping-layer fix.
|
|
478
|
-
# ---------------------------------------------------------------------------
|
|
479
|
-
|
|
480
|
-
@test "P364: backtick-bearing double-quoted --body permits when marker keyed on the unescaped logical body" {
|
|
481
|
-
# Logical draft body (plain backticks) — what the agent wraps in <draft>
|
|
482
|
-
# and what the mark hook hashes via compute_external_comms_key.
|
|
483
|
-
LOGICAL='Fixed in `compute_external_comms_key` per the patch on Node 20.'
|
|
484
|
-
SURFACE="gh-issue-comment"
|
|
485
|
-
KEY=$(printf '%s\n%s' "$LOGICAL" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
486
|
-
touch "${RDIR}/external-comms-risk-reviewed-${KEY}"
|
|
487
|
-
|
|
488
|
-
# The orchestrator backslash-escapes the backticks to survive bash double
|
|
489
|
-
# quotes — this is the byte-different command text the gate actually sees.
|
|
490
|
-
CMD='gh issue comment 42 --body "Fixed in \`compute_external_comms_key\` per the patch on Node 20."'
|
|
491
|
-
INPUT=$(build_bash_input "$CMD")
|
|
492
|
-
run_hook "$INPUT"
|
|
493
|
-
[ "$status" -eq 0 ]
|
|
494
|
-
[ -z "$output" ]
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
@test "P364: escaped backslash adjacent to escaped backtick (\\\\\\\`) unescapes to literal backslash + backtick (single left-to-right pass)" {
|
|
498
|
-
# Adjacency edge case (architect note): \\\` is escaped-backslash THEN
|
|
499
|
-
# escaped-backtick and must unescape to a literal backslash followed by a
|
|
500
|
-
# literal backtick — NOT mis-collapsed. The logical body therefore carries
|
|
501
|
-
# one backslash then one backtick.
|
|
502
|
-
LOGICAL=$'edge: \\`token here'
|
|
503
|
-
SURFACE="gh-issue-comment"
|
|
504
|
-
KEY=$(printf '%s\n%s' "$LOGICAL" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
505
|
-
touch "${RDIR}/external-comms-risk-reviewed-${KEY}"
|
|
506
|
-
|
|
507
|
-
# Command text carries \\\` (escaped backslash + escaped backtick).
|
|
508
|
-
CMD='gh issue comment 42 --body "edge: \\\`token here"'
|
|
509
|
-
INPUT=$(build_bash_input "$CMD")
|
|
510
|
-
run_hook "$INPUT"
|
|
511
|
-
[ "$status" -eq 0 ]
|
|
512
|
-
[ -z "$output" ]
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
@test "P364: single-quoted --body with literal backticks stays literal (no unescaping applied)" {
|
|
516
|
-
# Inside single quotes bash does NOT process backslashes or backticks, so
|
|
517
|
-
# the orchestrator writes plain backticks and the captured body already
|
|
518
|
-
# equals the logical draft. The fix must NOT alter the single-quote path.
|
|
519
|
-
LOGICAL='Fixed in `plain_span` here.'
|
|
520
|
-
SURFACE="gh-issue-comment"
|
|
521
|
-
KEY=$(printf '%s\n%s' "$LOGICAL" "$SURFACE" | shasum -a 256 | cut -d' ' -f1)
|
|
522
|
-
touch "${RDIR}/external-comms-risk-reviewed-${KEY}"
|
|
523
|
-
|
|
524
|
-
INPUT=$(build_bash_input "gh issue comment 42 --body 'Fixed in \`plain_span\` here.'")
|
|
525
|
-
run_hook "$INPUT"
|
|
526
|
-
[ "$status" -eq 0 ]
|
|
527
|
-
[ -z "$output" ]
|
|
528
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
# Behavioural tests for compute_external_comms_key substance-aware draft
|
|
3
|
-
# normalization (P276 — external-comms gate marker over-fires on PASS-class
|
|
4
|
-
# content edits).
|
|
5
|
-
#
|
|
6
|
-
# Contract (ADR-009 + ADR-028 amended 2026-06-06, ratified): the marker key
|
|
7
|
-
# must survive TRIVIAL whitespace-class edits to the draft body — interior
|
|
8
|
-
# CRLF/CR line endings and per-line trailing whitespace — so a PASS review
|
|
9
|
-
# does not have to re-fire on a no-op reformat. The conservative boundary is
|
|
10
|
-
# preserved: single-numeral edits and frontmatter-key changes remain
|
|
11
|
-
# SUBSTANTIVE (the key changes → review re-fires) so the leak-detection
|
|
12
|
-
# guarantee is never weakened.
|
|
13
|
-
#
|
|
14
|
-
# These exercise compute_external_comms_key directly (its stdout = the 64-char
|
|
15
|
-
# sha256 key) — behavioural assertions on the function's output, not a
|
|
16
|
-
# structural grep of the source.
|
|
17
|
-
|
|
18
|
-
setup() {
|
|
19
|
-
HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
20
|
-
# shellcheck source=../lib/external-comms-key.sh
|
|
21
|
-
source "$HOOKS_DIR/lib/external-comms-key.sh"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
# ---------- Trivial edits must NOT change the key (P276) ----------
|
|
25
|
-
|
|
26
|
-
@test "P276: interior CRLF line endings produce the same key as LF" {
|
|
27
|
-
lf=$(compute_external_comms_key $'Line one\nLine two\nLine three' 'gh-issue-comment')
|
|
28
|
-
crlf=$(compute_external_comms_key $'Line one\r\nLine two\r\nLine three' 'gh-issue-comment')
|
|
29
|
-
[ -n "$lf" ]
|
|
30
|
-
[ "$lf" = "$crlf" ]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@test "P276: per-line trailing whitespace produces the same key as clean lines" {
|
|
34
|
-
clean=$(compute_external_comms_key $'Line one\nLine two' 'gh-issue-comment')
|
|
35
|
-
trailing=$(compute_external_comms_key $'Line one \nLine two\t' 'gh-issue-comment')
|
|
36
|
-
[ -n "$clean" ]
|
|
37
|
-
[ "$clean" = "$trailing" ]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@test "P276: trailing whitespace at end of draft produces the same key (regression guard for prior rstrip)" {
|
|
41
|
-
clean=$(compute_external_comms_key 'A short draft body' 'gh-issue-comment')
|
|
42
|
-
trailed=$(compute_external_comms_key $'A short draft body\n\n ' 'gh-issue-comment')
|
|
43
|
-
[ "$clean" = "$trailed" ]
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# ---------- Conservative boundary: substantive edits MUST change the key ----------
|
|
47
|
-
|
|
48
|
-
@test "P276 boundary: a single-numeral edit changes the key (review re-fires)" {
|
|
49
|
-
before=$(compute_external_comms_key 'We support 12 plugins today' 'gh-issue-comment')
|
|
50
|
-
after=$(compute_external_comms_key 'We support 11 plugins today' 'gh-issue-comment')
|
|
51
|
-
[ "$before" != "$after" ]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@test "P276 boundary: a word/content edit changes the key (review re-fires)" {
|
|
55
|
-
before=$(compute_external_comms_key 'The release is ready' 'gh-issue-comment')
|
|
56
|
-
after=$(compute_external_comms_key 'The release is delayed' 'gh-issue-comment')
|
|
57
|
-
[ "$before" != "$after" ]
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
# ---------- Surface binding + changeset frontmatter strip preserved ----------
|
|
61
|
-
|
|
62
|
-
@test "P276: the surface remains part of the key (same body, different surface → different key)" {
|
|
63
|
-
a=$(compute_external_comms_key 'Same body text' 'gh-issue-comment')
|
|
64
|
-
b=$(compute_external_comms_key 'Same body text' 'gh-pr-comment')
|
|
65
|
-
[ "$a" != "$b" ]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@test "P276: changeset frontmatter strip still holds — full content keys equal the body-only key" {
|
|
69
|
-
body=$'Add the substance-aware normalization to the external-comms key.'
|
|
70
|
-
full=$'---\n"@windyroad/risk-scorer": patch\n---\n\n'"$body"
|
|
71
|
-
body_key=$(compute_external_comms_key "$body" 'changeset-author')
|
|
72
|
-
full_key=$(compute_external_comms_key "$full" 'changeset-author')
|
|
73
|
-
[ "$body_key" = "$full_key" ]
|
|
74
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
# Tests for git-push-gate.sh — gh pr merge block and release:watch guidance
|
|
3
|
-
|
|
4
|
-
setup() {
|
|
5
|
-
HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
6
|
-
HOOK="$HOOKS_DIR/git-push-gate.sh"
|
|
7
|
-
|
|
8
|
-
TEST_SESSION="bats-push-gate-$$-${BATS_TEST_NUMBER}"
|
|
9
|
-
# Ensure a clean risk dir
|
|
10
|
-
RDIR="${TMPDIR:-/tmp}/claude-risk-${TEST_SESSION}"
|
|
11
|
-
rm -rf "$RDIR"
|
|
12
|
-
mkdir -p "$RDIR"
|
|
13
|
-
|
|
14
|
-
# Create a temp project dir for package.json detection
|
|
15
|
-
TEST_PROJECT_DIR="$(mktemp -d)"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
teardown() {
|
|
19
|
-
rm -rf "$RDIR"
|
|
20
|
-
rm -rf "$TEST_PROJECT_DIR"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
# Helper: build a PreToolUse Bash input with a given command
|
|
24
|
-
build_input() {
|
|
25
|
-
local cmd="$1"
|
|
26
|
-
cat <<ENDJSON
|
|
27
|
-
{
|
|
28
|
-
"session_id": "$TEST_SESSION",
|
|
29
|
-
"tool_name": "Bash",
|
|
30
|
-
"tool_input": {
|
|
31
|
-
"command": "$cmd"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
ENDJSON
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@test "gh pr merge is blocked with release:watch guidance when script exists" {
|
|
38
|
-
# Create a package.json with release:watch
|
|
39
|
-
cat > "$TEST_PROJECT_DIR/package.json" <<'PKG'
|
|
40
|
-
{ "scripts": { "release:watch": "bash scripts/release-watch.sh" } }
|
|
41
|
-
PKG
|
|
42
|
-
|
|
43
|
-
INPUT=$(build_input "gh pr merge 4 --merge")
|
|
44
|
-
run bash -c "cd '$TEST_PROJECT_DIR' && echo '$INPUT' | '$HOOK'"
|
|
45
|
-
[ "$status" -eq 0 ]
|
|
46
|
-
[[ "$output" == *"permissionDecision"* ]]
|
|
47
|
-
[[ "$output" == *"deny"* ]]
|
|
48
|
-
[[ "$output" == *"release:watch"* ]]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@test "gh pr merge tells agent to create release:watch when script missing" {
|
|
52
|
-
# Create a package.json WITHOUT release:watch
|
|
53
|
-
cat > "$TEST_PROJECT_DIR/package.json" <<'PKG'
|
|
54
|
-
{ "scripts": { "test": "echo test" } }
|
|
55
|
-
PKG
|
|
56
|
-
|
|
57
|
-
INPUT=$(build_input "gh pr merge 4 --merge")
|
|
58
|
-
run bash -c "cd '$TEST_PROJECT_DIR' && echo '$INPUT' | '$HOOK'"
|
|
59
|
-
[ "$status" -eq 0 ]
|
|
60
|
-
[[ "$output" == *"permissionDecision"* ]]
|
|
61
|
-
[[ "$output" == *"deny"* ]]
|
|
62
|
-
# Should tell agent to create the script
|
|
63
|
-
[[ "$output" == *"no release:watch script"* ]]
|
|
64
|
-
[[ "$output" == *"gh pr merge"* ]]
|
|
65
|
-
[[ "$output" == *"gh run watch"* ]]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@test "gh pr merge tells agent to create release:watch when no package.json" {
|
|
69
|
-
local empty_dir="$(mktemp -d)"
|
|
70
|
-
|
|
71
|
-
INPUT=$(build_input "gh pr merge 4 --merge")
|
|
72
|
-
run bash -c "cd '$empty_dir' && echo '$INPUT' | '$HOOK'"
|
|
73
|
-
[ "$status" -eq 0 ]
|
|
74
|
-
[[ "$output" == *"permissionDecision"* ]]
|
|
75
|
-
[[ "$output" == *"deny"* ]]
|
|
76
|
-
# Should tell agent to create the script
|
|
77
|
-
[[ "$output" == *"no release:watch script"* ]]
|
|
78
|
-
[[ "$output" == *"gh pr merge"* ]]
|
|
79
|
-
[[ "$output" == *"gh run watch"* ]]
|
|
80
|
-
|
|
81
|
-
rm -rf "$empty_dir"
|
|
82
|
-
}
|