@windyroad/risk-scorer 0.3.5-preview.185 → 0.3.5-preview.188

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.
@@ -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 for structured decision prompts.
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 — machine-readable for structured AskUserQuestion prompts in calling skills):
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 — machine-readable for structured AskUserQuestion prompts in calling skills):
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 structured block is the only output for above-appetite guidance.
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,26 @@ 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. Match only markdown-table column-header
109
+ # rows so prose mentions of "action_class" (e.g. "No structured
110
+ # action_class column.") do not trip the assertion (P114).
111
+ run grep -qE '^\| *action_class\b' "$PIPELINE"
112
+ [ "$status" -ne 0 ]
113
+ }
114
+
115
+ @test "wip.md RISK_REMEDIATIONS format has no action_class column" {
116
+ run grep -qE '^\| *action_class\b' "$WIP"
117
+ [ "$status" -ne 0 ]
118
+ }
119
+
120
+ @test "plan.md RISK_REMEDIATIONS format has no action_class column" {
121
+ run grep -qE '^\| *action_class\b' "$PLAN"
122
+ [ "$status" -ne 0 ]
123
+ }
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 — machine-readable for structured AskUserQuestion prompts in calling skills):
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 as prose. The structured block is the only output for above-appetite guidance.
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
 
@@ -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:-1800}"
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
@@ -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:-1800}"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/risk-scorer",
3
- "version": "0.3.5-preview.185",
3
+ "version": "0.3.5-preview.188",
4
4
  "description": "Pipeline risk scoring, commit/push gates, and secret leak detection",
5
5
  "bin": {
6
6
  "windyroad-risk-scorer": "./bin/install.mjs"
@@ -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