@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,234 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
# Tests for the CI-status precondition in the push/release gate.
|
|
3
|
-
#
|
|
4
|
-
# Closes P208 (known-error): git-push-gate.sh did not consult CI health
|
|
5
|
-
# before scoring push/release risk, so a push could land on a CI-red
|
|
6
|
-
# master and a release could ship broken code.
|
|
7
|
-
#
|
|
8
|
-
# Contract:
|
|
9
|
-
# - `check_ci_status` queries `gh run list --branch <current-branch>
|
|
10
|
-
# --limit 1 --json status,conclusion,databaseId,url` for the current
|
|
11
|
-
# branch and returns 0 (allow) / 1 (deny).
|
|
12
|
-
# - Deny on conclusion ∈ {failure, cancelled, timed_out, action_required,
|
|
13
|
-
# startup_failure}.
|
|
14
|
-
# - Deny on status ∈ {queued, in_progress, pending, requested, waiting}.
|
|
15
|
-
# - Allow on conclusion ∈ {success, skipped, neutral} or unknown.
|
|
16
|
-
# - Empty array (no CI history yet) → allow. Handles the documented
|
|
17
|
-
# "first push triggers CI" case naturally — no bypass marker required.
|
|
18
|
-
# - `gh` failure (auth/timeout/API error) → DENY (fail-closed per the
|
|
19
|
-
# safe-high-fix-risk classifier on P208).
|
|
20
|
-
# - `${RDIR}/ci-bypass-${ACTION}` one-shot bypass marker — consumed on
|
|
21
|
-
# use, same family as reducing-push / incident-release.
|
|
22
|
-
# - Integration: in git-push-gate.sh, the ordering is bypass-markers →
|
|
23
|
-
# CI status → risk gate. The `incident-release` bypass MUST short-
|
|
24
|
-
# circuit BEFORE the CI check fires (per JTBD-201 + ADR-018).
|
|
25
|
-
|
|
26
|
-
setup() {
|
|
27
|
-
HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
28
|
-
source "$HOOKS_DIR/lib/gate-helpers.sh"
|
|
29
|
-
source "$HOOKS_DIR/lib/risk-gate.sh"
|
|
30
|
-
|
|
31
|
-
TEST_SESSION="bats-ci-gate-$$-${BATS_TEST_NUMBER}"
|
|
32
|
-
RDIR=$(_risk_dir "$TEST_SESSION")
|
|
33
|
-
rm -rf "$RDIR"
|
|
34
|
-
mkdir -p "$RDIR"
|
|
35
|
-
|
|
36
|
-
# Stand up a fake git repo so `git rev-parse --abbrev-ref HEAD` resolves.
|
|
37
|
-
TEST_REPO="$(mktemp -d)"
|
|
38
|
-
( cd "$TEST_REPO" && git init -q -b main && \
|
|
39
|
-
git -c user.email=t@e -c user.name=t commit --allow-empty -q -m "init" )
|
|
40
|
-
|
|
41
|
-
# Stub `gh` on PATH. The stub reads $FAKE_GH_OUTPUT and $FAKE_GH_EXIT
|
|
42
|
-
# for behaviour. PATH ordering: stub dir first.
|
|
43
|
-
STUB_DIR="$(mktemp -d)"
|
|
44
|
-
cat > "$STUB_DIR/gh" <<'STUB'
|
|
45
|
-
#!/bin/bash
|
|
46
|
-
if [ -n "${FAKE_GH_DELAY:-}" ]; then sleep "$FAKE_GH_DELAY"; fi
|
|
47
|
-
if [ -n "${FAKE_GH_OUTPUT:-}" ]; then
|
|
48
|
-
printf '%s' "$FAKE_GH_OUTPUT"
|
|
49
|
-
fi
|
|
50
|
-
exit "${FAKE_GH_EXIT:-0}"
|
|
51
|
-
STUB
|
|
52
|
-
chmod +x "$STUB_DIR/gh"
|
|
53
|
-
# `timeout` may not exist on the path on some macOS setups — stub a
|
|
54
|
-
# passthrough for portability. Tests inject FAKE_GH_DELAY only when
|
|
55
|
-
# they specifically test timeout behaviour.
|
|
56
|
-
ORIG_PATH="$PATH"
|
|
57
|
-
export PATH="$STUB_DIR:$PATH"
|
|
58
|
-
export TEST_REPO STUB_DIR
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
teardown() {
|
|
62
|
-
rm -rf "$RDIR" "$TEST_REPO" "$STUB_DIR"
|
|
63
|
-
export PATH="$ORIG_PATH"
|
|
64
|
-
unset FAKE_GH_OUTPUT FAKE_GH_EXIT FAKE_GH_DELAY CI_GATE_REASON CI_GATE_CATEGORY 2>/dev/null || true
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
# Run check_ci_status inside the fake repo so branch resolution works.
|
|
68
|
-
_run_check() {
|
|
69
|
-
local action="$1"
|
|
70
|
-
CI_GATE_REASON=""
|
|
71
|
-
CI_GATE_CATEGORY=""
|
|
72
|
-
( cd "$TEST_REPO" && \
|
|
73
|
-
FAKE_GH_OUTPUT="${FAKE_GH_OUTPUT:-}" FAKE_GH_EXIT="${FAKE_GH_EXIT:-0}" \
|
|
74
|
-
bash -c "source '$HOOKS_DIR/lib/gate-helpers.sh'; source '$HOOKS_DIR/lib/risk-gate.sh'; \
|
|
75
|
-
if check_ci_status '$TEST_SESSION' '$action'; then echo ALLOW; \
|
|
76
|
-
else echo \"DENY: \$CI_GATE_REASON\"; fi" )
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
@test "check_ci_status allows when latest CI run concluded success" {
|
|
80
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"success","databaseId":1,"url":"https://github.com/x/y/actions/runs/1"}]'
|
|
81
|
-
result=$(_run_check "push")
|
|
82
|
-
[[ "$result" == "ALLOW" ]]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@test "check_ci_status denies when latest CI run concluded failure (names conclusion + URL)" {
|
|
86
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"failure","databaseId":2,"url":"https://github.com/x/y/actions/runs/2"}]'
|
|
87
|
-
result=$(_run_check "push")
|
|
88
|
-
[[ "$result" == DENY:* ]]
|
|
89
|
-
[[ "$result" == *"failure"* ]]
|
|
90
|
-
[[ "$result" == *"https://github.com/x/y/actions/runs/2"* ]]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
@test "check_ci_status denies when latest CI run concluded cancelled" {
|
|
94
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"cancelled","databaseId":3,"url":"https://github.com/x/y/actions/runs/3"}]'
|
|
95
|
-
result=$(_run_check "release")
|
|
96
|
-
[[ "$result" == DENY:* ]]
|
|
97
|
-
[[ "$result" == *"cancelled"* ]]
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@test "check_ci_status denies when latest CI run concluded timed_out" {
|
|
101
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"timed_out","databaseId":4,"url":"https://github.com/x/y/actions/runs/4"}]'
|
|
102
|
-
result=$(_run_check "push")
|
|
103
|
-
[[ "$result" == DENY:* ]]
|
|
104
|
-
[[ "$result" == *"timed_out"* ]]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
@test "check_ci_status denies when latest CI run status is in_progress" {
|
|
108
|
-
export FAKE_GH_OUTPUT='[{"status":"in_progress","conclusion":null,"databaseId":5,"url":"https://github.com/x/y/actions/runs/5"}]'
|
|
109
|
-
result=$(_run_check "push")
|
|
110
|
-
[[ "$result" == DENY:* ]]
|
|
111
|
-
[[ "$result" == *"in_progress"* ]]
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
@test "check_ci_status denies when latest CI run status is queued" {
|
|
115
|
-
export FAKE_GH_OUTPUT='[{"status":"queued","conclusion":null,"databaseId":6,"url":"https://github.com/x/y/actions/runs/6"}]'
|
|
116
|
-
result=$(_run_check "release")
|
|
117
|
-
[[ "$result" == DENY:* ]]
|
|
118
|
-
[[ "$result" == *"queued"* ]]
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
@test "check_ci_status allows when CI history is empty (first push triggers CI)" {
|
|
122
|
-
export FAKE_GH_OUTPUT='[]'
|
|
123
|
-
result=$(_run_check "push")
|
|
124
|
-
[[ "$result" == "ALLOW" ]]
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@test "check_ci_status denies when gh exits non-zero (fail-closed, safe-high-fix-risk)" {
|
|
128
|
-
export FAKE_GH_OUTPUT=''
|
|
129
|
-
export FAKE_GH_EXIT=1
|
|
130
|
-
result=$(_run_check "push")
|
|
131
|
-
[[ "$result" == DENY:* ]]
|
|
132
|
-
# P377/RFC-029: ci-bypass removed — the deny states there is no override
|
|
133
|
-
# and does not instruct the user to create/touch a bypass marker.
|
|
134
|
-
[[ "$result" == *"no override"* ]]
|
|
135
|
-
[[ "$result" != *"touch "* ]]
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@test "P377/RFC-029: a ci-bypass marker is IGNORED — red CI still denies (override removed)" {
|
|
139
|
-
: > "$RDIR/ci-bypass-push"
|
|
140
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"failure","databaseId":7,"url":"https://github.com/x/y/actions/runs/7"}]'
|
|
141
|
-
result=$(_run_check "push")
|
|
142
|
-
# The marker no longer short-circuits; red CI is fail-closed regardless.
|
|
143
|
-
[[ "$result" == DENY:* ]]
|
|
144
|
-
# And the gate does not consume / act on the marker.
|
|
145
|
-
[ -f "$RDIR/ci-bypass-push" ]
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@test "check_ci_status allows when conclusion is skipped" {
|
|
149
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"skipped","databaseId":9,"url":"https://github.com/x/y/actions/runs/9"}]'
|
|
150
|
-
result=$(_run_check "push")
|
|
151
|
-
[[ "$result" == "ALLOW" ]]
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
@test "check_ci_status allows when conclusion is neutral" {
|
|
155
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"neutral","databaseId":10,"url":"https://github.com/x/y/actions/runs/10"}]'
|
|
156
|
-
result=$(_run_check "release")
|
|
157
|
-
[[ "$result" == "ALLOW" ]]
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
# ---------------------------------------------------------------------------
|
|
161
|
-
# Integration: git-push-gate.sh ordering — bypass-markers → CI status → risk
|
|
162
|
-
# gate. JTBD-201 demands the incident-release bypass MUST short-circuit
|
|
163
|
-
# BEFORE the new CI-status check fires.
|
|
164
|
-
# ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
# Helper: build a PreToolUse Bash input with a given command
|
|
167
|
-
_build_input() {
|
|
168
|
-
local cmd="$1"
|
|
169
|
-
cat <<JSON
|
|
170
|
-
{
|
|
171
|
-
"session_id": "$TEST_SESSION",
|
|
172
|
-
"tool_name": "Bash",
|
|
173
|
-
"tool_input": {
|
|
174
|
-
"command": "$cmd"
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
JSON
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
@test "git-push-gate.sh denies push:watch when CI is red even if risk score is within appetite" {
|
|
181
|
-
# Within-appetite risk score
|
|
182
|
-
echo "1" > "$RDIR/push"
|
|
183
|
-
# Disable drift check (no stored hash file)
|
|
184
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"failure","databaseId":11,"url":"https://github.com/x/y/actions/runs/11"}]'
|
|
185
|
-
|
|
186
|
-
INPUT=$(_build_input "npm run push:watch")
|
|
187
|
-
output=$( cd "$TEST_REPO" && echo "$INPUT" | \
|
|
188
|
-
FAKE_GH_OUTPUT="$FAKE_GH_OUTPUT" PATH="$STUB_DIR:$PATH" \
|
|
189
|
-
"$HOOKS_DIR/git-push-gate.sh" )
|
|
190
|
-
[[ "$output" == *"permissionDecision"* ]]
|
|
191
|
-
[[ "$output" == *"deny"* ]]
|
|
192
|
-
[[ "$output" == *"failure"* ]]
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
@test "git-push-gate.sh denies release:watch when CI is red even if risk score is within appetite" {
|
|
196
|
-
echo "1" > "$RDIR/release"
|
|
197
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"failure","databaseId":12,"url":"https://github.com/x/y/actions/runs/12"}]'
|
|
198
|
-
|
|
199
|
-
INPUT=$(_build_input "npm run release:watch")
|
|
200
|
-
output=$( cd "$TEST_REPO" && echo "$INPUT" | \
|
|
201
|
-
FAKE_GH_OUTPUT="$FAKE_GH_OUTPUT" PATH="$STUB_DIR:$PATH" \
|
|
202
|
-
"$HOOKS_DIR/git-push-gate.sh" )
|
|
203
|
-
[[ "$output" == *"permissionDecision"* ]]
|
|
204
|
-
[[ "$output" == *"deny"* ]]
|
|
205
|
-
[[ "$output" == *"failure"* ]]
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
@test "git-push-gate.sh allows release:watch when incident-release bypass is set, even if CI is red (JTBD-201)" {
|
|
209
|
-
echo "9" > "$RDIR/release"
|
|
210
|
-
: > "$RDIR/incident-release"
|
|
211
|
-
# Even with a red CI conclusion, the incident bypass must short-circuit
|
|
212
|
-
# both the CI check and the risk threshold.
|
|
213
|
-
export FAKE_GH_OUTPUT='[{"status":"completed","conclusion":"failure","databaseId":13,"url":"https://github.com/x/y/actions/runs/13"}]'
|
|
214
|
-
|
|
215
|
-
INPUT=$(_build_input "npm run release:watch")
|
|
216
|
-
output=$( cd "$TEST_REPO" && echo "$INPUT" | \
|
|
217
|
-
FAKE_GH_OUTPUT="$FAKE_GH_OUTPUT" PATH="$STUB_DIR:$PATH" \
|
|
218
|
-
"$HOOKS_DIR/git-push-gate.sh" )
|
|
219
|
-
# No permissionDecision means allow (exit 0 with no JSON).
|
|
220
|
-
[[ "$output" != *"permissionDecision"* ]]
|
|
221
|
-
# incident-release marker is one-shot — must be consumed
|
|
222
|
-
[ ! -f "$RDIR/incident-release" ]
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
@test "git-push-gate.sh allows push:watch when CI history is empty (first push)" {
|
|
226
|
-
echo "1" > "$RDIR/push"
|
|
227
|
-
export FAKE_GH_OUTPUT='[]'
|
|
228
|
-
|
|
229
|
-
INPUT=$(_build_input "npm run push:watch")
|
|
230
|
-
output=$( cd "$TEST_REPO" && echo "$INPUT" | \
|
|
231
|
-
FAKE_GH_OUTPUT="$FAKE_GH_OUTPUT" PATH="$STUB_DIR:$PATH" \
|
|
232
|
-
"$HOOKS_DIR/git-push-gate.sh" )
|
|
233
|
-
[[ "$output" != *"permissionDecision"* ]]
|
|
234
|
-
}
|