@windyroad/risk-scorer 0.3.5-preview.181 → 0.3.5-preview.187
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/pipeline.md +3 -2
- package/agents/plan.md +3 -2
- package/agents/test/risk-scorer-structured-remediations.bats +21 -0
- package/agents/wip.md +3 -2
- package/hooks/git-push-gate.sh +1 -1
- package/hooks/lib/risk-gate.sh +1 -1
- package/package.json +1 -1
- package/skills/create-risk/SKILL.md +172 -0
package/agents/pipeline.md
CHANGED
|
@@ -149,9 +149,9 @@ exceeds appetite. The only sanctioned above-appetite output is the Risk Report
|
|
|
149
149
|
structure, `RISK_SCORES: ...`, and the structured `RISK_REMEDIATIONS:` block
|
|
150
150
|
defined below.
|
|
151
151
|
|
|
152
|
-
Emit a structured `RISK_REMEDIATIONS:` block after the `RISK_SCORES:` line. This gives the calling skill machine-readable input
|
|
152
|
+
Emit a structured `RISK_REMEDIATIONS:` block after the `RISK_SCORES:` line. This gives the calling skill machine-readable input.
|
|
153
153
|
|
|
154
|
-
Format (5 columns
|
|
154
|
+
Format (5 columns):
|
|
155
155
|
```
|
|
156
156
|
RISK_REMEDIATIONS:
|
|
157
157
|
- R1 | <description of remediation> | <effort S/M/L> | <risk_delta -N> | <files affected>
|
|
@@ -161,6 +161,7 @@ RISK_REMEDIATIONS:
|
|
|
161
161
|
Column definitions:
|
|
162
162
|
- **effort**: estimated size of the remediation — S (< 1h, single file), M (1-4h, few files), L (> 4h, multiple files)
|
|
163
163
|
- **risk_delta**: estimated reduction in residual risk if this remediation is applied (e.g., `-3` means risk drops by 3 points)
|
|
164
|
+
- **description**: free-form prose. The agent reads this and decides what to do. No structured action_class column.
|
|
164
165
|
|
|
165
166
|
Include downstream back-pressure in the remediation list:
|
|
166
167
|
- **Commit**: If adding this commit would push the push queue risk >= 5, include a remediation to split the commit.
|
package/agents/plan.md
CHANGED
|
@@ -55,7 +55,7 @@ not policy-authorised — the only sanctioned FAIL output is the Plan Risk Repor
|
|
|
55
55
|
the `RISK_VERDICT: FAIL` marker, and the structured `RISK_REMEDIATIONS:` block
|
|
56
56
|
defined below.
|
|
57
57
|
|
|
58
|
-
Emit a structured `RISK_REMEDIATIONS:` block after the verdict (5 columns
|
|
58
|
+
Emit a structured `RISK_REMEDIATIONS:` block after the verdict (5 columns):
|
|
59
59
|
```
|
|
60
60
|
RISK_REMEDIATIONS:
|
|
61
61
|
- R1 | <description of what the plan must add/change> | <effort S/M/L> | <risk_delta -N> | <affected area>
|
|
@@ -64,8 +64,9 @@ RISK_REMEDIATIONS:
|
|
|
64
64
|
Column definitions:
|
|
65
65
|
- **effort**: estimated size of the remediation — S (< 1h, single file), M (1-4h, few files), L (> 4h, multiple files)
|
|
66
66
|
- **risk_delta**: estimated reduction in residual risk if this remediation is applied
|
|
67
|
+
- **description**: free-form prose. The agent reads this and decides what to do. No structured action_class column.
|
|
67
68
|
|
|
68
|
-
Do NOT emit free-text "consider" or "you should" prose. The
|
|
69
|
+
Do NOT emit free-text "consider" or "you should" prose outside the structured block. The `RISK_REMEDIATIONS:` block is the only output for above-appetite guidance.
|
|
69
70
|
|
|
70
71
|
## Control Discovery
|
|
71
72
|
|
|
@@ -98,3 +98,24 @@ setup() {
|
|
|
98
98
|
run grep -q "risk_delta" "$PLAN"
|
|
99
99
|
[ "$status" -eq 0 ]
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
# P108: scorer writes prose descriptions; agent decides (ADR-042 Rule 2a)
|
|
104
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
@test "pipeline.md RISK_REMEDIATIONS format has no action_class column" {
|
|
107
|
+
# ADR-042 Rule 2a: no structured action_class column. The agent reads
|
|
108
|
+
# the description and decides.
|
|
109
|
+
run grep -q "action_class" "$PIPELINE"
|
|
110
|
+
[ "$status" -ne 0 ]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@test "wip.md RISK_REMEDIATIONS format has no action_class column" {
|
|
114
|
+
run grep -q "action_class" "$WIP"
|
|
115
|
+
[ "$status" -ne 0 ]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@test "plan.md RISK_REMEDIATIONS format has no action_class column" {
|
|
119
|
+
run grep -q "action_class" "$PLAN"
|
|
120
|
+
[ "$status" -ne 0 ]
|
|
121
|
+
}
|
package/agents/wip.md
CHANGED
|
@@ -61,7 +61,7 @@ structured `RISK_REMEDIATIONS:` block defined below.
|
|
|
61
61
|
|
|
62
62
|
Provide the assessment table, then emit a structured `RISK_REMEDIATIONS:` block with specific risk-reducing actions:
|
|
63
63
|
|
|
64
|
-
Format (5 columns
|
|
64
|
+
Format (5 columns):
|
|
65
65
|
```
|
|
66
66
|
RISK_REMEDIATIONS:
|
|
67
67
|
- R1 | Commit current changes to move WIP forward | S | -2 | <uncommitted files>
|
|
@@ -73,8 +73,9 @@ RISK_REMEDIATIONS:
|
|
|
73
73
|
Column definitions:
|
|
74
74
|
- **effort**: estimated size of the remediation — S (< 1h, single file), M (1-4h, few files), L (> 4h, multiple files)
|
|
75
75
|
- **risk_delta**: estimated reduction in residual risk if this remediation is applied (e.g., `-3` means risk drops by 3 points)
|
|
76
|
+
- **description**: free-form prose. The agent reads this and decides what to do. No structured action_class column.
|
|
76
77
|
|
|
77
|
-
Do NOT emit free-text suggestions
|
|
78
|
+
Do NOT emit free-text suggestions outside the structured block. The `RISK_REMEDIATIONS:` block is the only output for above-appetite guidance.
|
|
78
79
|
|
|
79
80
|
The verdict is `RISK_VERDICT: PAUSE`. This blocks the next edit until the risk is addressed.
|
|
80
81
|
|
package/hooks/git-push-gate.sh
CHANGED
|
@@ -53,7 +53,7 @@ if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*npm run push:watch(\s|$)'; then
|
|
|
53
53
|
PUSH_NOW=$(date +%s)
|
|
54
54
|
PUSH_SCORE_TIME=$(_mtime "$PUSH_SCORE_FILE")
|
|
55
55
|
PUSH_AGE=$(( PUSH_NOW - PUSH_SCORE_TIME ))
|
|
56
|
-
PUSH_TTL="${RISK_TTL:-
|
|
56
|
+
PUSH_TTL="${RISK_TTL:-3600}"
|
|
57
57
|
if [ "$PUSH_AGE" -ge "$PUSH_TTL" ]; then
|
|
58
58
|
risk_gate_deny "Push blocked: Push risk score expired (${PUSH_AGE}s old, TTL ${PUSH_TTL}s). Delegate to risk-scorer to rescore."
|
|
59
59
|
exit 0
|
package/hooks/lib/risk-gate.sh
CHANGED
|
@@ -17,7 +17,7 @@ check_risk_gate() {
|
|
|
17
17
|
RDIR=$(_risk_dir "$SESSION_ID")
|
|
18
18
|
local SCORE_FILE="${RDIR}/${ACTION}"
|
|
19
19
|
local HASH_FILE="${RDIR}/state-hash"
|
|
20
|
-
local TTL_SECONDS="${RISK_TTL:-
|
|
20
|
+
local TTL_SECONDS="${RISK_TTL:-3600}"
|
|
21
21
|
|
|
22
22
|
# 1. Score file must exist (fail-closed)
|
|
23
23
|
if [ ! -f "$SCORE_FILE" ]; then
|
package/package.json
CHANGED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wr-risk-scorer:create-risk
|
|
3
|
+
description: Create a new standing-risk entry in docs/risks/. Examines existing risks, gathers impact/likelihood/controls from the user, writes a file matching docs/risks/TEMPLATE.md, and updates the register index.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Risk Register Entry Generator
|
|
8
|
+
|
|
9
|
+
Create a new standing-risk file in `docs/risks/` following the format defined by `docs/risks/TEMPLATE.md`. The register captures persistent risks (distinct from the ephemeral per-change reports in `.risk-reports/`), and its criteria come from `RISK-POLICY.md`.
|
|
10
|
+
|
|
11
|
+
This skill is the invocation surface for populating the register (scaffolded by P033; populated per P102). Per ADR-015, it is a plugin-namespaced on-demand skill. Per ADR-014, the skill commits its own work.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
### 1. Discover existing risks
|
|
16
|
+
|
|
17
|
+
Scan for existing risk files:
|
|
18
|
+
- Glob `docs/risks/R*.md` (skip `README.md`, `TEMPLATE.md`)
|
|
19
|
+
- Note the highest numbered risk to determine the next sequence number
|
|
20
|
+
- Read any risks related to the topic being discussed (if the user has mentioned a topic)
|
|
21
|
+
- If `docs/risks/` does not exist, explain that `/wr-risk-scorer:update-policy` must be run first (it ships the scaffolding) and stop
|
|
22
|
+
|
|
23
|
+
### 2. Gather context from the user
|
|
24
|
+
|
|
25
|
+
You MUST use the AskUserQuestion tool to collect context that cannot be derived. Do not proceed to step 3 until you have answers. Apply ADR-013 Rule 6 non-interactive defaults if the tool is unavailable (AFK mode): choose the most conservative option for each question and note auto-selection in the output.
|
|
26
|
+
|
|
27
|
+
Auto-derive where possible (do not ask):
|
|
28
|
+
- **ID number** — next free slot per step 3 (do not ask per `feedback_dont_ask_trivial_id_choices.md`).
|
|
29
|
+
- **Today's date** — use the current date for `Identified` and `Last reviewed`.
|
|
30
|
+
- **Category** — infer from description keywords where unambiguous: "token", "secret", "leak" → `infosec`; "install", "hook", "pipeline" → `operational`. Confirm only if ambiguous.
|
|
31
|
+
- **Next review** — default to 6 months from today.
|
|
32
|
+
|
|
33
|
+
Ask the user (one AskUserQuestion call with grouped questions):
|
|
34
|
+
|
|
35
|
+
1. **What is the risk?** A short title and 1-2 paragraph description — what could go wrong, for whom, and why it matters. This is the condition, not the control.
|
|
36
|
+
2. **Impact level (from `RISK-POLICY.md`)?** 1 Negligible · 2 Minor · 3 Moderate · 4 Significant · 5 Severe. Read the policy's Impact table to the user if they need the descriptions.
|
|
37
|
+
3. **Likelihood level?** 1 Rare · 2 Unlikely · 3 Possible · 4 Likely · 5 Almost certain.
|
|
38
|
+
4. **Existing controls?** Each control names what it does and where it is implemented (file path or `ADR-NNN`). If none, leave empty.
|
|
39
|
+
5. **Residual impact and likelihood** (after controls). If controls are minimal, residual = inherent — do not fabricate reductions. Per ADR-026, quantitative reduction claims must cite evidence (test, hook gate, pipeline report). If no evidence, state "Residual same as inherent pending control evidence" in the Treatment section and set residual = inherent.
|
|
40
|
+
6. **Treatment choice?** Accept · Mitigate · Transfer · Avoid. Include brief justification.
|
|
41
|
+
7. **Owner?** Persona or role (e.g. `solo-developer`, `plugin-maintainer`, `tech-lead`).
|
|
42
|
+
|
|
43
|
+
If the user has already provided this context in the conversation (e.g. as arguments, or as part of a pipeline-finding hand-off), use what they have given and only ask about what is missing.
|
|
44
|
+
|
|
45
|
+
### 3. Determine sequence number and filename
|
|
46
|
+
|
|
47
|
+
- Next number = **max of the local and origin highest risk numbers**, plus 1 (or 001 if none exist).
|
|
48
|
+
- Filename: `R<NNN>-<kebab-case-title>.active.md`
|
|
49
|
+
- Pad the number to 3 digits (001, 002, ... 010, 011, etc.)
|
|
50
|
+
|
|
51
|
+
**Why compare against origin?** Per ADR-019 confirmation criterion 2, ticket-creator skills MUST re-check next-number assignment against `git ls-tree origin/<base>` before assigning. Without it, parallel sessions can mint the same ID for different risks, causing a destructive surgical rebase on push.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Local-max number
|
|
55
|
+
local_max=$(ls docs/risks/R*.md 2>/dev/null | sed 's/.*\///' | grep -oE '^R[0-9]+' | sed 's/^R//' | sort -n | tail -1)
|
|
56
|
+
|
|
57
|
+
# Origin-max number — reads remote-tracking ref. `--name-only` required per P056
|
|
58
|
+
# to avoid false-matches on blob SHAs.
|
|
59
|
+
origin_max=$(git ls-tree --name-only origin/main docs/risks/ 2>/dev/null | sed 's|^docs/risks/||' | grep -oE '^R[0-9]+' | sed 's/^R//' | sort -n | tail -1)
|
|
60
|
+
|
|
61
|
+
# Take the max of the two and increment.
|
|
62
|
+
next=$(printf '%03d' $(( $(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If the local choice would have collided with an origin risk file created since the last fetch, the `git ls-tree` lookup catches it here and the renumber is automatic. Log the renumber in the user-facing report (e.g. "Bumped next risk number from R012 → R013 to avoid collision with origin").
|
|
66
|
+
|
|
67
|
+
### 4. Compute scores and bands
|
|
68
|
+
|
|
69
|
+
Use the Risk Matrix from `RISK-POLICY.md`:
|
|
70
|
+
|
|
71
|
+
- **Inherent Score** = Impact × Likelihood
|
|
72
|
+
- **Residual Score** = Impact × Likelihood (after controls)
|
|
73
|
+
- **Band** (for each) per the Label Bands table: 1-2 Very Low · 3-4 Low · 5-9 Medium · 10-16 High · 17-25 Very High
|
|
74
|
+
- **Within appetite?** = residual score ≤ `RISK-POLICY.md`'s appetite threshold (read the threshold at runtime; do not hardcode)
|
|
75
|
+
|
|
76
|
+
### 5. Write the risk file
|
|
77
|
+
|
|
78
|
+
Write the file to `docs/risks/` using the structure from `TEMPLATE.md`:
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
# Risk R<NNN>: <Title>
|
|
82
|
+
|
|
83
|
+
**Status**: Active
|
|
84
|
+
**Category**: <infosec | operational | brand | delivery>
|
|
85
|
+
**Identified**: <YYYY-MM-DD>
|
|
86
|
+
**Owner**: <persona or role>
|
|
87
|
+
**Last reviewed**: <YYYY-MM-DD>
|
|
88
|
+
**Next review**: <YYYY-MM-DD + 6 months>
|
|
89
|
+
|
|
90
|
+
## Description
|
|
91
|
+
|
|
92
|
+
<1-2 paragraph description from step 2.>
|
|
93
|
+
|
|
94
|
+
## Inherent Risk
|
|
95
|
+
|
|
96
|
+
Impact × Likelihood *before* controls.
|
|
97
|
+
|
|
98
|
+
- **Impact**: <level> (<label>)
|
|
99
|
+
- **Likelihood**: <level> (<label>)
|
|
100
|
+
- **Inherent Score**: <product>
|
|
101
|
+
- **Inherent Band**: <band>
|
|
102
|
+
|
|
103
|
+
## Controls
|
|
104
|
+
|
|
105
|
+
- **<control-name>** — <what it does>. Implemented in <file path or ADR-NNN>.
|
|
106
|
+
|
|
107
|
+
## Residual Risk
|
|
108
|
+
|
|
109
|
+
Impact × Likelihood *after* controls.
|
|
110
|
+
|
|
111
|
+
- **Impact**: <level> (<label>)
|
|
112
|
+
- **Likelihood**: <level> (<label>)
|
|
113
|
+
- **Residual Score**: <product>
|
|
114
|
+
- **Residual Band**: <band>
|
|
115
|
+
- **Within appetite?**: <Yes | No>
|
|
116
|
+
|
|
117
|
+
## Treatment
|
|
118
|
+
|
|
119
|
+
<Accept | Mitigate | Transfer | Avoid>. <Justification.>
|
|
120
|
+
|
|
121
|
+
## Monitoring
|
|
122
|
+
|
|
123
|
+
- **Trigger to re-assess**: <event or threshold>
|
|
124
|
+
- **Metrics**: <if any>
|
|
125
|
+
|
|
126
|
+
## Related
|
|
127
|
+
|
|
128
|
+
- Criteria: `RISK-POLICY.md`
|
|
129
|
+
- Realised-as: <links to `docs/problems/P<NNN>` if any>
|
|
130
|
+
- Treatment ADRs: <links if any>
|
|
131
|
+
- Personas affected: <links to `docs/jtbd/<persona>/persona.md`>
|
|
132
|
+
|
|
133
|
+
## Change Log
|
|
134
|
+
|
|
135
|
+
- <YYYY-MM-DD>: Initial identification.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 6. Update the register index
|
|
139
|
+
|
|
140
|
+
`docs/risks/README.md` has a **Register** table that MUST reflect the new risk. Append a row with the following columns:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
| R<NNN> | <Title> | <Category> | <Inherent Score> | <Residual Score> | <Treatment verb> | <Owner> | <Next review date> |
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This step is not optional: the README drifts from the register without it, and the ISO 27001 audit signal depends on the index being accurate.
|
|
147
|
+
|
|
148
|
+
### 7. Confirm with the user
|
|
149
|
+
|
|
150
|
+
Present the written file path, inherent/residual bands, and any `Within appetite?: No` flag. Ask via AskUserQuestion:
|
|
151
|
+
|
|
152
|
+
1. Does the description accurately capture the risk?
|
|
153
|
+
2. Are the inherent and residual scores defensible?
|
|
154
|
+
3. Is the treatment choice appropriate for the residual band?
|
|
155
|
+
4. Should the owner or next review date be adjusted?
|
|
156
|
+
|
|
157
|
+
Apply any feedback by editing the file and re-updating the README row if scores/treatment change.
|
|
158
|
+
|
|
159
|
+
### 8. Commit the risk (ADR-014)
|
|
160
|
+
|
|
161
|
+
Per ADR-014, this skill commits its own work. Stage both files and commit:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
git add docs/risks/R<NNN>-<title>.active.md docs/risks/README.md
|
|
165
|
+
git commit -m "docs(risks): open R<NNN> <title>"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The commit message convention `docs(risks): open R<NNN> <title>` matches `docs/risks/README.md` step 6 and mirrors `docs(problems): open P<NNN>` used by `/wr-itil:manage-problem`.
|
|
169
|
+
|
|
170
|
+
If the commit-gate pattern-matches `git commit` text and blocks, run `/wr-risk-scorer:assess-release` first to produce a fresh pipeline marker, then retry the commit.
|
|
171
|
+
|
|
172
|
+
$ARGUMENTS
|