@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.
Files changed (55) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +13 -2
  5. package/bin/install.mjs +42 -8
  6. package/hooks/codex-agent-completion.mjs +82 -0
  7. package/hooks/hooks.json +5 -2
  8. package/hooks/risk-scorer-dispatch.sh +3 -0
  9. package/hooks/risk-scorer-scaffold-nudge.sh +15 -4
  10. package/package.json +5 -2
  11. package/scripts/codex-agents.mjs +208 -0
  12. package/scripts/sync-codex-skills.mjs +8 -3
  13. package/skills/assess-external-comms/SKILL.md +5 -4
  14. package/skills/assess-inbound-report/SKILL.md +5 -4
  15. package/skills/assess-release/SKILL.md +5 -4
  16. package/skills/assess-wip/SKILL.md +5 -4
  17. package/skills/bootstrap-catalog/SKILL.md +5 -4
  18. package/skills/create-risk/SKILL.md +5 -4
  19. package/skills/external-comms/SKILL.md +5 -4
  20. package/skills/pipeline/SKILL.md +5 -4
  21. package/skills/update-policy/SKILL.md +6 -5
  22. package/skills/wip/SKILL.md +5 -4
  23. package/agents/test/inbound-report-contract.bats +0 -225
  24. package/agents/test/risk-scorer-above-appetite-stop.bats +0 -80
  25. package/agents/test/risk-scorer-catalog-consumption.bats +0 -138
  26. package/agents/test/risk-scorer-commit-verdict.bats +0 -66
  27. package/agents/test/risk-scorer-monitoring-not-a-control.bats +0 -76
  28. package/agents/test/risk-scorer-reducing-bypass-criteria.bats +0 -62
  29. package/agents/test/risk-scorer-register-hint.bats +0 -111
  30. package/agents/test/risk-scorer-structured-remediations.bats +0 -123
  31. package/agents/test/risk-scorer-user-stated-preconditions.bats +0 -89
  32. package/hooks/test/ci-status-gate.bats +0 -234
  33. package/hooks/test/external-comms-gate.bats +0 -528
  34. package/hooks/test/external-comms-key-substance.bats +0 -74
  35. package/hooks/test/git-push-gate.bats +0 -82
  36. package/hooks/test/pipeline-state-changeset-partition.bats +0 -181
  37. package/hooks/test/pipeline-state-hash.bats +0 -195
  38. package/hooks/test/plan-risk-guidance-once-per-session.bats +0 -95
  39. package/hooks/test/reducing-marker-persistence.bats +0 -236
  40. package/hooks/test/risk-gate.bats +0 -330
  41. package/hooks/test/risk-policy-project-root.bats +0 -20
  42. package/hooks/test/risk-score-commit-gate-adr-031-bypass.bats +0 -98
  43. package/hooks/test/risk-score-commit-gate-cadence-staleness.bats +0 -105
  44. package/hooks/test/risk-score-mark-external-comms-prompt-parse.bats +0 -94
  45. package/hooks/test/risk-score-mark-register-queue.bats +0 -253
  46. package/hooks/test/risk-score-mark.bats +0 -112
  47. package/hooks/test/risk-scorer-hook-dispatch.bats +0 -77
  48. package/hooks/test/risk-scorer-no-stop-hook.bats +0 -19
  49. package/hooks/test/risk-scorer-scaffold-nudge.bats +0 -132
  50. package/hooks/test/slide-marker-on-subprocess-return.bats +0 -118
  51. package/hooks/test/wip-risk-mark.bats +0 -64
  52. package/scripts/test/drain-register-queue.bats +0 -359
  53. package/scripts/test/evaluate-graduation.bats +0 -533
  54. package/scripts/test/extract-risks-from-reports.bats +0 -366
  55. 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
- }