@windyroad/risk-scorer 0.2.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/.claude-plugin/plugin.json +5 -0
- package/agents/agent.md +21 -0
- package/agents/pipeline.md +152 -0
- package/agents/plan.md +85 -0
- package/agents/policy.md +43 -0
- package/agents/wip.md +79 -0
- package/bin/install.mjs +42 -0
- package/hooks/git-push-gate.sh +117 -0
- package/hooks/hooks.json +24 -0
- package/hooks/lib/gate-helpers.sh +174 -0
- package/hooks/lib/pipeline-state.sh +318 -0
- package/hooks/lib/risk-gate.sh +85 -0
- package/hooks/plan-risk-guidance.sh +52 -0
- package/hooks/risk-hash-refresh.sh +28 -0
- package/hooks/risk-policy-enforce-edit.sh +42 -0
- package/hooks/risk-policy-reset-marker.sh +17 -0
- package/hooks/risk-score-commit-gate.sh +64 -0
- package/hooks/risk-score-mark.sh +120 -0
- package/hooks/risk-score-plan-enforce.sh +31 -0
- package/hooks/risk-score-reset.sh +17 -0
- package/hooks/risk-score.sh +29 -0
- package/hooks/secret-leak-gate.sh +72 -0
- package/hooks/test/risk-gate.bats +107 -0
- package/hooks/wip-risk-gate.sh +44 -0
- package/hooks/wip-risk-mark.sh +32 -0
- package/package.json +28 -0
- package/skills/wr:risk-policy/SKILL.md +178 -0
package/agents/agent.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent
|
|
3
|
+
description: Risk scoring routing doc. Use mode-specific agents instead.
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Glob
|
|
7
|
+
model: inherit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Risk Scorer
|
|
11
|
+
|
|
12
|
+
This agent has been split into mode-specific agents. Use the appropriate one:
|
|
13
|
+
|
|
14
|
+
| Mode | Agent | When to use |
|
|
15
|
+
|------|-------|-------------|
|
|
16
|
+
| Pipeline scoring | `risk-scorer-pipeline` | Commit, push, release, changeset risk assessment |
|
|
17
|
+
| Policy review | `risk-scorer-policy` | Validate RISK-POLICY.md drafts |
|
|
18
|
+
| Plan review | `risk-scorer-plan` | Review implementation plans for risk |
|
|
19
|
+
| WIP nudge | `risk-scorer-wip` | Assess cumulative risk after each edit |
|
|
20
|
+
|
|
21
|
+
If invoked directly (subagent_type: 'risk-scorer'), default to pipeline scoring mode — read `risk-scorer-pipeline.md` and follow its instructions.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pipeline
|
|
3
|
+
description: Scores pipeline actions (commit, push, release) for cumulative residual risk per RISK-POLICY.md.
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Glob
|
|
7
|
+
model: inherit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are the Risk Scorer in pipeline scoring mode. You assess commit, push, and release actions using cumulative 3-layer risk scoring.
|
|
11
|
+
|
|
12
|
+
## Score Output (MANDATORY)
|
|
13
|
+
|
|
14
|
+
Do NOT write score files yourself. A PostToolUse hook reads your output and writes files deterministically.
|
|
15
|
+
|
|
16
|
+
Your report MUST end with a structured `RISK_SCORES` block. This is how the hook knows what to write:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
RISK_SCORES: commit=N push=N release=N
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Where N is the integer residual risk score (0-25) for each layer.
|
|
23
|
+
|
|
24
|
+
If the action is risk-reducing or risk-neutral, add on a separate line:
|
|
25
|
+
```
|
|
26
|
+
RISK_BYPASS: reducing
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
For live incidents, use:
|
|
30
|
+
```
|
|
31
|
+
RISK_BYPASS: incident
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If scores or bypass lines are missing, the commit/push/release gates will block.
|
|
35
|
+
|
|
36
|
+
## Pipeline State
|
|
37
|
+
|
|
38
|
+
You receive structured pipeline state context with these sections:
|
|
39
|
+
|
|
40
|
+
- **UNCOMMITTED CHANGES**: Diff stat, untracked files, and categories
|
|
41
|
+
- **UNPUSHED CHANGES**: Commits and cumulative diff between remote and HEAD
|
|
42
|
+
- **UNRELEASED CHANGES**: Changeset count and cumulative diff
|
|
43
|
+
- **STALE FILES**: Modified files uncommitted for over 24h
|
|
44
|
+
|
|
45
|
+
## Cumulative Risk Report
|
|
46
|
+
|
|
47
|
+
The report MUST assess risk cumulatively, building up from the release queue:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
## Pipeline Risk Report
|
|
51
|
+
|
|
52
|
+
### Layer 1: Unreleased Changes (release risk)
|
|
53
|
+
- Scope: [what's in the unreleased queue]
|
|
54
|
+
- Risks: [itemised risks]
|
|
55
|
+
- **Residual risk: N/25 (Label)**
|
|
56
|
+
|
|
57
|
+
### Layer 2: Unreleased + Unpushed (push risk)
|
|
58
|
+
- Scope: [unreleased + unpushed combined]
|
|
59
|
+
- Additional risks from unpushed changes: [new risks]
|
|
60
|
+
- **Cumulative residual risk: N/25 (Label)**
|
|
61
|
+
|
|
62
|
+
### Layer 3: Unreleased + Unpushed + Uncommitted (commit risk)
|
|
63
|
+
- Scope: [all three layers combined]
|
|
64
|
+
- Additional risks from uncommitted changes: [new risks]
|
|
65
|
+
- **Cumulative residual risk: N/25 (Label)**
|
|
66
|
+
|
|
67
|
+
### Pipeline Summary
|
|
68
|
+
| Layer | Scope | Residual Risk |
|
|
69
|
+
|-------|-------|--------------|
|
|
70
|
+
| Unreleased | [brief] | N/25 (Label) |
|
|
71
|
+
| + Unpushed | [brief] | N/25 (Label) |
|
|
72
|
+
| + Uncommitted | [brief] | N/25 (Label) |
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Commit score >= push score >= release score (risk accumulates upward).
|
|
76
|
+
|
|
77
|
+
### Risk Item Format
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
#### Risk N: [Short description]
|
|
81
|
+
- Inherent impact: N/5 (Label) - [why]
|
|
82
|
+
- Inherent likelihood: N/5 (Label) - [why]
|
|
83
|
+
- Inherent risk: N/25 (Label)
|
|
84
|
+
- Controls:
|
|
85
|
+
- [Specific test file/scenario or hook name] - reduces [dimension] from N to N because [rationale]
|
|
86
|
+
- **Residual risk: N/25 (Label)**
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Score File Values
|
|
90
|
+
|
|
91
|
+
- Commit score: Layer 3 cumulative (highest)
|
|
92
|
+
- Push score: Layer 2 cumulative
|
|
93
|
+
- Release score: Layer 1
|
|
94
|
+
|
|
95
|
+
## Risk-Reducing and Risk-Neutral Bypass
|
|
96
|
+
|
|
97
|
+
Assess whether each action is risk-reducing, risk-neutral, or risk-increasing. Include `RISK_BYPASS: reducing` in your output for reducing/neutral actions. Do not include it for risk-increasing actions.
|
|
98
|
+
|
|
99
|
+
For live incidents (outage, security, information disclosure), include `RISK_BYPASS: incident`.
|
|
100
|
+
|
|
101
|
+
## Downstream Back-Pressure
|
|
102
|
+
|
|
103
|
+
- **Commit**: If adding this commit would push the push queue risk >= 5, warn.
|
|
104
|
+
- **Push**: If pushing would push the release queue risk >= 5, warn.
|
|
105
|
+
|
|
106
|
+
## Confidential Information Disclosure
|
|
107
|
+
|
|
108
|
+
Check diffs for business metrics (revenue, user counts, pricing, traffic volumes). Flag as a standalone risk if found.
|
|
109
|
+
|
|
110
|
+
## Suggested Actions
|
|
111
|
+
|
|
112
|
+
If any cumulative risk >= 5, suggest specific actions referencing which layer is driving the risk.
|
|
113
|
+
|
|
114
|
+
## Report History
|
|
115
|
+
|
|
116
|
+
Do NOT save reports to `.risk-reports/` — the PostToolUse hook handles report persistence.
|
|
117
|
+
|
|
118
|
+
## Control Discovery
|
|
119
|
+
|
|
120
|
+
Do not rely on a static list. For each control claimed to reduce risk, you MUST:
|
|
121
|
+
1. Identify the specific failure scenario
|
|
122
|
+
2. Explain HOW the control exercises that exact scenario
|
|
123
|
+
3. Ask: "Would this control catch this failure before reaching the user?"
|
|
124
|
+
4. **Name the control**: "Tests pass" is not a control. Name the specific test file and scenario. If you cannot name it, it provides 0 reduction.
|
|
125
|
+
|
|
126
|
+
## Constraints
|
|
127
|
+
|
|
128
|
+
- You are a scorer, not an editor.
|
|
129
|
+
- Follow RISK-POLICY.md for impact levels and appetite.
|
|
130
|
+
- Never include `/tmp/` file paths in your report output.
|
|
131
|
+
|
|
132
|
+
## Likelihood Levels
|
|
133
|
+
|
|
134
|
+
| Level | Label | Description |
|
|
135
|
+
|-------|-------|-------------|
|
|
136
|
+
| 1 | Rare | Trivial, isolated, well-understood. |
|
|
137
|
+
| 2 | Unlikely | Straightforward, clear scope. |
|
|
138
|
+
| 3 | Possible | Moderate complexity, multiple concerns. |
|
|
139
|
+
| 4 | Likely | Complex, spans modules, hard to predict. |
|
|
140
|
+
| 5 | Almost certain | High-complexity, critical paths, wide dependencies. |
|
|
141
|
+
|
|
142
|
+
## Risk Matrix
|
|
143
|
+
|
|
144
|
+
| Impact \ Likelihood | 1 | 2 | 3 | 4 | 5 |
|
|
145
|
+
|---|---|---|---|---|---|
|
|
146
|
+
| 1 Negligible | 1 | 2 | 3 | 4 | 5 |
|
|
147
|
+
| 2 Minor | 2 | 4 | 6 | 8 | 10 |
|
|
148
|
+
| 3 Moderate | 3 | 6 | 9 | 12 | 15 |
|
|
149
|
+
| 4 Significant | 4 | 8 | 12 | 16 | 20 |
|
|
150
|
+
| 5 Severe | 5 | 10 | 15 | 20 | 25 |
|
|
151
|
+
|
|
152
|
+
Label Bands: 1-2 Very Low, 3-4 Low, 5-9 Medium, 10-16 High, 17-25 Very High.
|
package/agents/plan.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan
|
|
3
|
+
description: Reviews implementation plans for risk, including projected release risk.
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Glob
|
|
7
|
+
model: inherit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are the Risk Scorer in plan review mode. Assess both the plan's own risk AND the projected release risk.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. Read `RISK-POLICY.md` for impact levels and risk appetite
|
|
15
|
+
2. Read the plan file provided in the prompt
|
|
16
|
+
3. Gather current pipeline state: run `.claude/hooks/lib/pipeline-state.sh --all` to discover the unreleased queue
|
|
17
|
+
4. Assess the plan's own inherent risk against impact levels
|
|
18
|
+
5. Consider what controls will be in place (CI, hooks, tests, preview deploys)
|
|
19
|
+
6. Estimate the plan's own residual risk after controls
|
|
20
|
+
7. **Project release risk**: what would the release look like if the plan's changes were added to the existing unreleased queue?
|
|
21
|
+
8. **Apply back-pressure**: if projected release risk >= appetite and the plan doesn't include a release strategy, FAIL.
|
|
22
|
+
|
|
23
|
+
## Verdict Logic
|
|
24
|
+
|
|
25
|
+
- **PASS** if both the plan's own residual risk AND projected release risk are within appetite
|
|
26
|
+
- **FAIL** if either exceeds appetite — explain which and what the plan should include
|
|
27
|
+
|
|
28
|
+
## Output Format
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
## Plan Risk Report
|
|
32
|
+
|
|
33
|
+
### Plan's Own Risk
|
|
34
|
+
- Inherent risk: N/25 (Label)
|
|
35
|
+
- Controls: [list relevant controls]
|
|
36
|
+
- Residual risk: N/25 (Label)
|
|
37
|
+
|
|
38
|
+
### Projected Release Risk
|
|
39
|
+
- Current unreleased queue risk: N/25 (Label)
|
|
40
|
+
- Projected release risk (queue + this plan): N/25 (Label)
|
|
41
|
+
- Release strategy in plan: [present / missing]
|
|
42
|
+
- Back-pressure: [none / FAIL: plan must include release strategy]
|
|
43
|
+
|
|
44
|
+
### Verdict
|
|
45
|
+
- Plan residual risk: PASS/FAIL
|
|
46
|
+
- Projected release risk: PASS/FAIL
|
|
47
|
+
- Overall: PASS/FAIL
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
End your report with `RISK_VERDICT: PASS` or `RISK_VERDICT: FAIL` on its own line. A PostToolUse hook reads this and writes the marker files — do NOT write files yourself.
|
|
51
|
+
|
|
52
|
+
## Control Discovery
|
|
53
|
+
|
|
54
|
+
For each control claimed to reduce risk:
|
|
55
|
+
1. Identify the specific failure scenario
|
|
56
|
+
2. Name the specific test file/scenario or hook
|
|
57
|
+
3. If you cannot name it, it provides 0 reduction
|
|
58
|
+
|
|
59
|
+
## Constraints
|
|
60
|
+
|
|
61
|
+
- You are a scorer, not an editor.
|
|
62
|
+
- Follow RISK-POLICY.md for impact levels and appetite.
|
|
63
|
+
- Never include `/tmp/` file paths in your output.
|
|
64
|
+
|
|
65
|
+
## Likelihood Levels
|
|
66
|
+
|
|
67
|
+
| Level | Label | Description |
|
|
68
|
+
|-------|-------|-------------|
|
|
69
|
+
| 1 | Rare | Trivial, isolated, well-understood. |
|
|
70
|
+
| 2 | Unlikely | Straightforward, clear scope. |
|
|
71
|
+
| 3 | Possible | Moderate complexity, multiple concerns. |
|
|
72
|
+
| 4 | Likely | Complex, spans modules, hard to predict. |
|
|
73
|
+
| 5 | Almost certain | High-complexity, critical paths, wide dependencies. |
|
|
74
|
+
|
|
75
|
+
## Risk Matrix
|
|
76
|
+
|
|
77
|
+
| Impact \ Likelihood | 1 | 2 | 3 | 4 | 5 |
|
|
78
|
+
|---|---|---|---|---|---|
|
|
79
|
+
| 1 Negligible | 1 | 2 | 3 | 4 | 5 |
|
|
80
|
+
| 2 Minor | 2 | 4 | 6 | 8 | 10 |
|
|
81
|
+
| 3 Moderate | 3 | 6 | 9 | 12 | 15 |
|
|
82
|
+
| 4 Significant | 4 | 8 | 12 | 16 | 20 |
|
|
83
|
+
| 5 Severe | 5 | 10 | 15 | 20 | 25 |
|
|
84
|
+
|
|
85
|
+
Label Bands: 1-2 Very Low, 3-4 Low, 5-9 Medium, 10-16 High, 17-25 Very High.
|
package/agents/policy.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: policy
|
|
3
|
+
description: Validates RISK-POLICY.md drafts for ISO 31000 compliance.
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Glob
|
|
7
|
+
model: inherit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are the Risk Scorer in policy review mode. Validate a draft RISK-POLICY.md against ISO 31000 and the risk scoring system's requirements.
|
|
11
|
+
|
|
12
|
+
## Validation Checklist
|
|
13
|
+
|
|
14
|
+
1. **Impact levels**: Must describe business consequences, not file categories. Must use 5 labels: Negligible, Minor, Moderate, Significant, Severe. Must reference specific product features by name.
|
|
15
|
+
2. **Risk appetite**: Must define a numeric threshold as a residual risk score.
|
|
16
|
+
3. **Label alignment**: Impact level labels must match the risk matrix row labels exactly.
|
|
17
|
+
4. **Business context**: Should include who uses the product and how (ISO 31000 context establishment).
|
|
18
|
+
5. **Last reviewed date**: Must be present.
|
|
19
|
+
6. **Confidential information** (public repos): Should include a "Confidential Information" section. Check repo visibility with `gh repo view --json isPrivate`. A missing section is a warning, not a failure.
|
|
20
|
+
|
|
21
|
+
## Verdict
|
|
22
|
+
|
|
23
|
+
End your report with `RISK_VERDICT: PASS` or `RISK_VERDICT: FAIL` on its own line. A PostToolUse hook reads this and writes the marker files — do NOT write files yourself.
|
|
24
|
+
|
|
25
|
+
- PASS if the draft is compliant
|
|
26
|
+
- FAIL if it has issues — list the specific issues so the caller can fix them
|
|
27
|
+
|
|
28
|
+
## Constraints
|
|
29
|
+
|
|
30
|
+
- You are a reviewer, not an editor.
|
|
31
|
+
- Never include `/tmp/` file paths in your output.
|
|
32
|
+
|
|
33
|
+
## Risk Matrix (for label verification)
|
|
34
|
+
|
|
35
|
+
| Impact \ Likelihood | 1 | 2 | 3 | 4 | 5 |
|
|
36
|
+
|---|---|---|---|---|---|
|
|
37
|
+
| 1 Negligible | 1 | 2 | 3 | 4 | 5 |
|
|
38
|
+
| 2 Minor | 2 | 4 | 6 | 8 | 10 |
|
|
39
|
+
| 3 Moderate | 3 | 6 | 9 | 12 | 15 |
|
|
40
|
+
| 4 Significant | 4 | 8 | 12 | 16 | 20 |
|
|
41
|
+
| 5 Severe | 5 | 10 | 15 | 20 | 25 |
|
|
42
|
+
|
|
43
|
+
Label Bands: 1-2 Very Low, 3-4 Low, 5-9 Medium, 10-16 High, 17-25 Very High.
|
package/agents/wip.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wip
|
|
3
|
+
description: Assesses cumulative pipeline risk after each edit, providing guidance and recommendations.
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Glob
|
|
7
|
+
model: inherit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are the Risk Scorer in WIP nudge mode. Assess cumulative pipeline risk after a file edit and provide guidance.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. Read the edited file path from the prompt
|
|
15
|
+
2. Run `git diff --stat` to see all uncommitted changes (non-doc files)
|
|
16
|
+
3. Read the most recent push risk report from `.risk-reports/` (latest `*-push.md`)
|
|
17
|
+
4. Read the most recent release risk report from `.risk-reports/` (latest `*-release.md`)
|
|
18
|
+
5. Assess cumulative pipeline WIP risk:
|
|
19
|
+
- What uncommitted changes exist and their risk profile
|
|
20
|
+
- What the push and release reports say the top risks are
|
|
21
|
+
- Does the latest edit increase, decrease, or not affect cumulative risk?
|
|
22
|
+
6. Provide the cumulative risk picture and recommendations
|
|
23
|
+
7. End your report with `RISK_VERDICT: CONTINUE` or `RISK_VERDICT: PAUSE` on its own line
|
|
24
|
+
|
|
25
|
+
## Output
|
|
26
|
+
|
|
27
|
+
Always provide the cumulative risk picture:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
## WIP Risk Assessment
|
|
31
|
+
|
|
32
|
+
### Cumulative Pipeline Risk
|
|
33
|
+
| Layer | Risk |
|
|
34
|
+
|-------|------|
|
|
35
|
+
| Unreleased | N/25 (from latest release report) |
|
|
36
|
+
| + Unpushed | N/25 (from latest push report) |
|
|
37
|
+
| + Uncommitted | N/25 (your assessment) |
|
|
38
|
+
|
|
39
|
+
### This Edit
|
|
40
|
+
- File: [path]
|
|
41
|
+
- Effect: [increases / decreases / neutral to cumulative risk]
|
|
42
|
+
- Why: [brief explanation]
|
|
43
|
+
|
|
44
|
+
### Recommendations
|
|
45
|
+
- [specific guidance based on current pipeline state]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If cumulative risk is **within appetite** (< 5): provide the assessment and say "Continue." The verdict is CONTINUE.
|
|
49
|
+
|
|
50
|
+
If cumulative risk **exceeds appetite** (>= 5): provide specific risk-reducing suggestions:
|
|
51
|
+
- "Commit your current changes to move WIP forward"
|
|
52
|
+
- "Write tests for [risk item from report]" — name the specific risk and test file
|
|
53
|
+
- "The release report flags [X] — address it before adding more changes"
|
|
54
|
+
- "Push your commits to get CI feedback"
|
|
55
|
+
|
|
56
|
+
The verdict is PAUSE. This blocks the next edit until the risk is addressed.
|
|
57
|
+
|
|
58
|
+
## Control Discovery
|
|
59
|
+
|
|
60
|
+
For each control claimed to reduce risk, name the specific test file/scenario. If you cannot name it, it provides 0 reduction.
|
|
61
|
+
|
|
62
|
+
## Constraints
|
|
63
|
+
|
|
64
|
+
- You are a scorer, not an editor. Do NOT write files — a PostToolUse hook handles that.
|
|
65
|
+
- Follow RISK-POLICY.md for impact levels and appetite.
|
|
66
|
+
- Never include `/tmp/` file paths in your output.
|
|
67
|
+
- Save reports to `.risk-reports/` is NOT required in this mode.
|
|
68
|
+
|
|
69
|
+
## Risk Matrix
|
|
70
|
+
|
|
71
|
+
| Impact \ Likelihood | 1 | 2 | 3 | 4 | 5 |
|
|
72
|
+
|---|---|---|---|---|---|
|
|
73
|
+
| 1 Negligible | 1 | 2 | 3 | 4 | 5 |
|
|
74
|
+
| 2 Minor | 2 | 4 | 6 | 8 | 10 |
|
|
75
|
+
| 3 Moderate | 3 | 6 | 9 | 12 | 15 |
|
|
76
|
+
| 4 Significant | 4 | 8 | 12 | 16 | 20 |
|
|
77
|
+
| 5 Severe | 5 | 10 | 15 | 20 | 25 |
|
|
78
|
+
|
|
79
|
+
Label Bands: 1-2 Very Low, 3-4 Low, 5-9 Medium, 10-16 High, 17-25 Very High.
|
package/bin/install.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const utils = await import(resolve(__dirname, "../../shared/install-utils.mjs"));
|
|
8
|
+
|
|
9
|
+
const PLUGIN = "wr-risk-scorer";
|
|
10
|
+
const DEPS = [];
|
|
11
|
+
|
|
12
|
+
const flags = utils.parseStandardArgs(process.argv);
|
|
13
|
+
|
|
14
|
+
if (flags.help) {
|
|
15
|
+
console.log(`
|
|
16
|
+
Usage: npx @windyroad/risk-scorer [options]
|
|
17
|
+
|
|
18
|
+
Pipeline risk scoring, commit/push gates, and secret leak detection
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--update Update this plugin and its skills
|
|
22
|
+
--uninstall Remove this plugin
|
|
23
|
+
--dry-run Show what would be done without executing
|
|
24
|
+
--help, -h Show this help
|
|
25
|
+
`);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (flags.dryRun) {
|
|
30
|
+
utils.setDryRun(true);
|
|
31
|
+
console.log("[dry-run mode — no commands will be executed]\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
utils.checkPrerequisites();
|
|
35
|
+
|
|
36
|
+
if (flags.uninstall) {
|
|
37
|
+
utils.uninstallPackage(PLUGIN);
|
|
38
|
+
} else if (flags.update) {
|
|
39
|
+
utils.updatePackage(PLUGIN);
|
|
40
|
+
} else {
|
|
41
|
+
utils.installPackage(PLUGIN, { deps: DEPS });
|
|
42
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse hook for pipeline discipline:
|
|
3
|
+
# - Blocks bare `git push` and directs to npm run push:watch.
|
|
4
|
+
# - Gates `npm run push:watch` on push risk score (TTL + drift + threshold).
|
|
5
|
+
# - Gates `npx changeset` / `npm run changeset` on release + push risk (back-pressure).
|
|
6
|
+
# - Gates `npm run release:watch` on release risk score (TTL + drift + threshold).
|
|
7
|
+
# - Blocks `gh pr merge` and directs to npm run release:watch.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/lib/risk-gate.sh"
|
|
13
|
+
_enable_err_trap
|
|
14
|
+
|
|
15
|
+
_parse_input
|
|
16
|
+
|
|
17
|
+
TOOL_NAME=$(_get_tool_name)
|
|
18
|
+
[ "$TOOL_NAME" = "Bash" ] || exit 0
|
|
19
|
+
|
|
20
|
+
COMMAND=$(_get_command)
|
|
21
|
+
SESSION_ID=$(_get_session_id)
|
|
22
|
+
|
|
23
|
+
# Block git push to master/main/publish/changeset-release/*, or bare git push.
|
|
24
|
+
# Allow explicit pushes to other branches (feature branches etc).
|
|
25
|
+
if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*git push(\s|$)'; then
|
|
26
|
+
# Allow if pushing to an explicit branch that isn't a protected/managed branch
|
|
27
|
+
if echo "$COMMAND" | grep -qE 'git push\s+\S+\s+\S+' && \
|
|
28
|
+
! echo "$COMMAND" | grep -qE 'git push\s+\S+\s+(master|main|publish|changeset-release/)'; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
risk_gate_deny "Use \`npm run push:watch\` instead of \`git push\`. It pushes, watches the pipeline, and then surfaces either the release PR URL (if there are pending changesets) or the test deploy URL so you can review before releasing. The publish and changeset-release/* branches are managed by the pipeline -- do not push to them directly."
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Gate push:watch on push risk score
|
|
36
|
+
if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*npm run push:watch(\s|$)'; then
|
|
37
|
+
if [ -n "$SESSION_ID" ]; then
|
|
38
|
+
RDIR=$(_risk_dir "$SESSION_ID")
|
|
39
|
+
# Risk-reducing/neutral bypass for push
|
|
40
|
+
if [ -f "${RDIR}/reducing-push" ]; then
|
|
41
|
+
rm -f "${RDIR}/reducing-push"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
# Clean tree bypass: if no uncommitted changes, pushing existing commits is safe
|
|
45
|
+
if [ -f "${RDIR}/clean" ]; then
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
PUSH_SCORE_FILE="${RDIR}/push"
|
|
49
|
+
if [ ! -f "$PUSH_SCORE_FILE" ]; then
|
|
50
|
+
risk_gate_deny "Push blocked: No push risk score found. Delegate to risk-scorer-pipeline (subagent_type: 'risk-scorer-pipeline') to assess cumulative pipeline risk."
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
PUSH_NOW=$(date +%s)
|
|
54
|
+
PUSH_SCORE_TIME=$(_mtime "$PUSH_SCORE_FILE")
|
|
55
|
+
PUSH_AGE=$(( PUSH_NOW - PUSH_SCORE_TIME ))
|
|
56
|
+
PUSH_TTL="${RISK_TTL:-1800}"
|
|
57
|
+
if [ "$PUSH_AGE" -ge "$PUSH_TTL" ]; then
|
|
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
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
PUSH_SCORE=$(cat "$PUSH_SCORE_FILE" 2>/dev/null || echo "")
|
|
62
|
+
if ! echo "$PUSH_SCORE" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
|
|
63
|
+
risk_gate_deny "Push blocked: Push risk score is not yet available (scoring in progress). Wait a moment and retry."
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
PUSH_DENIED=$(python3 -c "print('yes' if float('${PUSH_SCORE}') >= 5 else 'no')" 2>/dev/null || echo "no")
|
|
67
|
+
if [ "$PUSH_DENIED" = "yes" ]; then
|
|
68
|
+
risk_gate_deny "Push blocked: Push risk score ${PUSH_SCORE}/25 (Medium or above). To proceed: (1) release first via \`npm run release:watch\`, (2) split the push, or (3) add risk-reducing measures. If risk-neutral or risk-reducing, delegate to risk-scorer-pipeline (subagent_type: 'risk-scorer-pipeline') — it will create a bypass marker."
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Gate changeset creation on release risk score (fail-closed).
|
|
76
|
+
# Changesets feed directly into releases, so gate on the release score.
|
|
77
|
+
if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*(npx changeset|npm run changeset)(\s|$)'; then
|
|
78
|
+
if [ -n "$SESSION_ID" ]; then
|
|
79
|
+
if ! check_risk_gate "$SESSION_ID" "release"; then
|
|
80
|
+
risk_gate_deny "Changeset blocked: ${RISK_GATE_REASON}"
|
|
81
|
+
exit 0
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Gate release:watch on release risk score
|
|
88
|
+
if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*npm run release:watch(\s|$)'; then
|
|
89
|
+
if [ -n "$SESSION_ID" ]; then
|
|
90
|
+
RDIR=$(_risk_dir "$SESSION_ID")
|
|
91
|
+
# Live-incident bypass: if an incident marker exists, allow release
|
|
92
|
+
# regardless of risk score. Used when addressing outages, security
|
|
93
|
+
# incidents, or information disclosure that requires immediate deployment.
|
|
94
|
+
if [ -f "${RDIR}/incident-release" ]; then
|
|
95
|
+
rm -f "${RDIR}/incident-release"
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
# Risk-reducing bypass for release
|
|
99
|
+
if [ -f "${RDIR}/reducing-release" ]; then
|
|
100
|
+
rm -f "${RDIR}/reducing-release"
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
if ! check_risk_gate "$SESSION_ID" "release"; then
|
|
104
|
+
risk_gate_deny "Release blocked: ${RISK_GATE_REASON}. To proceed: (1) split the release, (2) add risk-reducing measures, or (3) for a LIVE INCIDENT, delegate to risk-scorer-pipeline (subagent_type: 'risk-scorer-pipeline') with incident context for an incident bypass."
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
108
|
+
exit 0
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Match gh pr merge. Should go via npm run release:watch instead.
|
|
112
|
+
if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*gh pr merge(\s|$)'; then
|
|
113
|
+
risk_gate_deny "Use \`npm run release:watch\` instead of \`gh pr merge\`. It merges the release PR, watches the publish pipeline, and surfaces the production URL when live -- or tells you what failed and how to fix it."
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
exit 0
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"UserPromptSubmit": [
|
|
4
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-score.sh" }] }
|
|
5
|
+
],
|
|
6
|
+
"PreToolUse": [
|
|
7
|
+
{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/secret-leak-gate.sh" }, { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/wip-risk-gate.sh" }] },
|
|
8
|
+
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/git-push-gate.sh" }] },
|
|
9
|
+
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-score-commit-gate.sh" }] },
|
|
10
|
+
{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-policy-enforce-edit.sh" }] },
|
|
11
|
+
{ "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-score-plan-enforce.sh" }] },
|
|
12
|
+
{ "matcher": "EnterPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/plan-risk-guidance.sh" }] }
|
|
13
|
+
],
|
|
14
|
+
"PostToolUse": [
|
|
15
|
+
{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/wip-risk-mark.sh" }] },
|
|
16
|
+
{ "matcher": "Agent", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-score-mark.sh" }] },
|
|
17
|
+
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-hash-refresh.sh" }] }
|
|
18
|
+
],
|
|
19
|
+
"Stop": [
|
|
20
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-score-reset.sh" }] },
|
|
21
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/risk-policy-reset-marker.sh" }] }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|