@windyroad/risk-scorer 0.19.12 → 0.19.13-preview.1285
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.
|
@@ -159,6 +159,7 @@ EOF
|
|
|
159
159
|
# ---------- Surface detection ----------
|
|
160
160
|
SURFACE=""
|
|
161
161
|
DRAFT=""
|
|
162
|
+
BODY_FILE_ERROR=0
|
|
162
163
|
|
|
163
164
|
case "$TOOL_NAME" in
|
|
164
165
|
Bash)
|
|
@@ -169,6 +170,15 @@ try:
|
|
|
169
170
|
except Exception:
|
|
170
171
|
print('')
|
|
171
172
|
" 2>/dev/null || echo "")
|
|
173
|
+
TOOL_CWD=$(printf '%s' "$INPUT" | python3 -c "
|
|
174
|
+
import sys, json
|
|
175
|
+
try:
|
|
176
|
+
event = json.load(sys.stdin)
|
|
177
|
+
tool = event.get('tool_input', {})
|
|
178
|
+
print(tool.get('cwd') or tool.get('workdir') or event.get('cwd') or '')
|
|
179
|
+
except Exception:
|
|
180
|
+
print('')
|
|
181
|
+
" 2>/dev/null || echo "")
|
|
172
182
|
|
|
173
183
|
# Surface match — most-specific first.
|
|
174
184
|
if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*gh issue create(\s|$)'; then
|
|
@@ -230,13 +240,78 @@ except Exception:
|
|
|
230
240
|
# Then --body / --field for the gh + npm + security-advisories surfaces.
|
|
231
241
|
# Then -m / --message for git commit (single-line literal forms).
|
|
232
242
|
#
|
|
233
|
-
# When absent (npm publish,
|
|
234
|
-
#
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
import sys, re
|
|
243
|
+
# When absent (npm publish, editor flow already filtered), DRAFT=""
|
|
244
|
+
# is acceptable. A named body file must be readable before review.
|
|
245
|
+
if ! DRAFT=$(printf '%s' "$COMMAND" | python3 -c "
|
|
246
|
+
import sys, re, os, shlex
|
|
238
247
|
cmd = sys.stdin.read()
|
|
239
248
|
surface = sys.argv[1]
|
|
249
|
+
tool_cwd = sys.argv[2]
|
|
250
|
+
if surface.startswith('gh-'):
|
|
251
|
+
try:
|
|
252
|
+
lexer = shlex.shlex(cmd, posix=True, punctuation_chars=';&|')
|
|
253
|
+
lexer.whitespace_split = True
|
|
254
|
+
lexer.commenters = ''
|
|
255
|
+
args = list(lexer)
|
|
256
|
+
except ValueError:
|
|
257
|
+
if '--body-file' in cmd or ' -F' in cmd or ' -b' in cmd:
|
|
258
|
+
raise SystemExit(2)
|
|
259
|
+
args = []
|
|
260
|
+
try:
|
|
261
|
+
base = os.path.abspath(tool_cwd) if os.path.isabs(tool_cwd) else ''
|
|
262
|
+
if len(args) >= 4 and args[0] == 'cd' and args[2] == '&&':
|
|
263
|
+
if os.path.isabs(args[1]):
|
|
264
|
+
base = os.path.abspath(args[1])
|
|
265
|
+
elif base:
|
|
266
|
+
base = os.path.abspath(os.path.join(base, args[1]))
|
|
267
|
+
args = args[3:]
|
|
268
|
+
# Skip known option values so a quoted title/body beginning with -F
|
|
269
|
+
# cannot be mistaken for a second body source.
|
|
270
|
+
valued = {'--title', '-t', '--template', '-T', '--repo', '-R',
|
|
271
|
+
'--assignee', '-a', '--label', '-l', '--reviewer', '-r',
|
|
272
|
+
'--milestone', '-m', '--project', '-p', '--base', '-B', '--head', '-H'}
|
|
273
|
+
sources = []
|
|
274
|
+
i = 3
|
|
275
|
+
while i < len(args):
|
|
276
|
+
arg = args[i]
|
|
277
|
+
if arg and all(ch in ';&|' for ch in arg):
|
|
278
|
+
raise ValueError('ambiguous command')
|
|
279
|
+
if arg in ('--body-file', '-F', '--body', '-b'):
|
|
280
|
+
sources.append(('file' if arg in ('--body-file', '-F') else 'body',
|
|
281
|
+
args[i + 1] if i + 1 < len(args) else '', arg))
|
|
282
|
+
i += 2
|
|
283
|
+
elif arg.startswith('--body-file=') or arg.startswith('--body='):
|
|
284
|
+
sources.append(('file' if arg.startswith('--body-file=') else 'body',
|
|
285
|
+
arg.split('=', 1)[1], arg.split('=', 1)[0]))
|
|
286
|
+
i += 1
|
|
287
|
+
elif arg.startswith('-F') or arg.startswith('-b'):
|
|
288
|
+
sources.append(('file' if arg.startswith('-F') else 'body',
|
|
289
|
+
arg[3:] if arg[2:3] == '=' else arg[2:], arg[:2]))
|
|
290
|
+
i += 1
|
|
291
|
+
elif arg in valued:
|
|
292
|
+
i += 2
|
|
293
|
+
else:
|
|
294
|
+
i += 1
|
|
295
|
+
files = [value for kind, value, _ in sources if kind == 'file']
|
|
296
|
+
short_bodies = [value for kind, value, flag in sources if kind == 'body' and flag == '-b']
|
|
297
|
+
if files or short_bodies:
|
|
298
|
+
if args[:2] not in (['gh', 'issue'], ['gh', 'pr']):
|
|
299
|
+
raise ValueError('ambiguous command')
|
|
300
|
+
if files:
|
|
301
|
+
if len(files) != 1 or len(sources) != 1 or not files[0] or files[0] == '-':
|
|
302
|
+
raise ValueError('ambiguous body file')
|
|
303
|
+
if not os.path.isabs(files[0]) and not base:
|
|
304
|
+
raise ValueError('unknown command checkout')
|
|
305
|
+
path = files[0] if os.path.isabs(files[0]) else os.path.join(base, files[0])
|
|
306
|
+
with open(path, encoding='utf-8') as body_file:
|
|
307
|
+
print(body_file.read(), end='')
|
|
308
|
+
else:
|
|
309
|
+
if len(sources) != 1 or not short_bodies[0]:
|
|
310
|
+
raise ValueError('ambiguous body')
|
|
311
|
+
print(short_bodies[0], end='')
|
|
312
|
+
raise SystemExit
|
|
313
|
+
except (OSError, UnicodeError, ValueError):
|
|
314
|
+
raise SystemExit(2)
|
|
240
315
|
# P364: bash double-quote unescape. The double-quoted body capture groups
|
|
241
316
|
# carry RAW shell-escaped command text — an orchestrator must backslash-escape
|
|
242
317
|
# backticks (and \$, \", \\) inside \"...\" to survive bash parsing, e.g.
|
|
@@ -310,7 +385,9 @@ for pat, flags, unescape in patterns:
|
|
|
310
385
|
body = unescape_dq(body)
|
|
311
386
|
print(body)
|
|
312
387
|
break
|
|
313
|
-
" "$SURFACE" 2>/dev/null
|
|
388
|
+
" "$SURFACE" "$TOOL_CWD" 2>/dev/null); then
|
|
389
|
+
BODY_FILE_ERROR=1
|
|
390
|
+
fi
|
|
314
391
|
;;
|
|
315
392
|
|
|
316
393
|
Write|Edit)
|
|
@@ -362,6 +439,11 @@ print(json.dumps({
|
|
|
362
439
|
" "$reason"
|
|
363
440
|
}
|
|
364
441
|
|
|
442
|
+
if [ "$BODY_FILE_ERROR" -eq 1 ]; then
|
|
443
|
+
deny_with_reason "BLOCKED (external-comms gate): body-file could not be read unambiguously from the command checkout. Use one readable UTF-8 --body-file path and review its exact contents before retrying."
|
|
444
|
+
exit 0
|
|
445
|
+
fi
|
|
446
|
+
|
|
365
447
|
permit_with_advisory() {
|
|
366
448
|
local msg="$1"
|
|
367
449
|
python3 -c "
|
package/hooks/lib/risk-gate.sh
CHANGED
|
@@ -39,7 +39,7 @@ check_risk_gate() {
|
|
|
39
39
|
# 1. Score file must exist (fail-closed)
|
|
40
40
|
if [ ! -f "$SCORE_FILE" ]; then
|
|
41
41
|
RISK_GATE_CATEGORY="missing"
|
|
42
|
-
RISK_GATE_REASON="No ${ACTION} risk score found. Delegate to wr-risk-scorer:pipeline to assess cumulative pipeline risk. On Codex,
|
|
42
|
+
RISK_GATE_REASON="No ${ACTION} risk score found. Delegate to wr-risk-scorer:pipeline to assess cumulative pipeline risk. On Codex, spawn a fresh typed reviewer for each new assessment; followup_task cannot refresh a completed review marker. Wait for the reviewer and confirm that exact target is completed; then invoke \`interrupt_agent\` exactly once on the completed target before retrying. If it is still running, keep waiting. The compatibility hook persists the completed structured verdict; no transcript parsing or nested codex exec is required. On Claude Code, dispatch the reviewer synchronously (\`run_in_background: false\`) before retrying."
|
|
43
43
|
return 1
|
|
44
44
|
fi
|
|
45
45
|
|
|
@@ -163,7 +163,7 @@ print(('yes' if score > N else 'no') + ' ' + str(N))
|
|
|
163
163
|
if [ "$DENIED" = "yes" ]; then
|
|
164
164
|
RISK_GATE_CATEGORY="threshold"
|
|
165
165
|
RISK_GATE_SCORE="$SCORE"
|
|
166
|
-
RISK_GATE_REASON="${ACTION} risk score ${SCORE}/25 exceeds the project appetite of ${APPETITE}/25 (RISK-POLICY.md). Reduce risk or halt — there is no proceed-anyway path (P377/RFC-029). To proceed within appetite: (1) split the ${ACTION}, (2) add risk-reducing measures and re-score, or (3) if this is incident response, delegate to wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') with incident context — it scores the change against the live realised-risk baseline (ADR-042 Rule 1b) and clears it via the risk-reducing path if it is net-risk-reducing."
|
|
166
|
+
RISK_GATE_REASON="${ACTION} risk score ${SCORE}/25 exceeds the project appetite of ${APPETITE}/25 (RISK-POLICY.md). Reduce risk or halt — there is no proceed-anyway path (P377/RFC-029). After remediation on Codex, spawn a fresh typed wr-risk-scorer:pipeline reviewer for this checkout; followup_task does not refresh the recorded score. To proceed within appetite: (1) split the ${ACTION}, (2) add risk-reducing measures and re-score, or (3) if this is incident response, delegate to wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') with incident context — it scores the change against the live realised-risk baseline (ADR-042 Rule 1b) and clears it via the risk-reducing path if it is net-risk-reducing."
|
|
167
167
|
return 1
|
|
168
168
|
fi
|
|
169
169
|
|
package/package.json
CHANGED
package/skills/pipeline/SKILL.md
CHANGED
|
@@ -34,7 +34,10 @@ prevents the completion bridge from binding the returned verdict. `$ARGUMENTS`
|
|
|
34
34
|
is already self-contained, so no forked conversation context is required.
|
|
35
35
|
After the Codex agent reports completion, invoke `interrupt_agent` exactly once
|
|
36
36
|
on that completed target so the compatibility hook receives the structured
|
|
37
|
-
result.
|
|
37
|
+
result. After remediation changes the assessed risk, spawn a **new** typed
|
|
38
|
+
reviewer with the updated state. `followup_task` on a completed reviewer does
|
|
39
|
+
not create a new marker generation, so its later score cannot replace the
|
|
40
|
+
recorded score. Do not relaunch the same target or inspect a transcript.
|
|
38
41
|
|
|
39
42
|
```
|
|
40
43
|
subagent_type: wr-risk-scorer:pipeline
|