@windyroad/risk-scorer 0.1.3 → 0.1.4-preview.27

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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @windyroad/risk-scorer
2
+
3
+ **Pipeline risk scoring, commit/push gates, and secret leak detection for Claude Code.** Scores every change for risk and blocks high-risk commits and pushes before they happen.
4
+
5
+ Part of [Windy Road Agent Plugins](../../README.md).
6
+
7
+ ## What It Does
8
+
9
+ The risk-scorer plugin brings ISO 31000-aligned risk management to your AI coding workflow. It:
10
+
11
+ 1. **Scores risk** on every edit, assessing cumulative pipeline risk as changes build up
12
+ 2. **Gates commits** -- blocks `git commit` when cumulative risk exceeds your policy threshold
13
+ 3. **Gates pushes** -- blocks `git push` for high-risk changesets (use `npm run push:watch` instead)
14
+ 4. **Detects secrets** -- scans edits for API keys, tokens, passwords, and other credentials before they're written
15
+ 5. **Reviews plans** -- scores implementation plans for risk before you start building
16
+
17
+ All thresholds are configurable through your project's `RISK-POLICY.md`.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npx @windyroad/risk-scorer
23
+ ```
24
+
25
+ Restart Claude Code after installing.
26
+
27
+ ## Usage
28
+
29
+ The plugin works automatically once installed. On first run in a project without a risk policy, it blocks edits and directs you to generate one:
30
+
31
+ ```
32
+ /wr-risk-scorer:update-policy
33
+ ```
34
+
35
+ This creates a `RISK-POLICY.md` tailored to your project, defining impact levels, likelihood scales, risk appetite, and the risk matrix -- all aligned to ISO 31000.
36
+
37
+ ## How It Works
38
+
39
+ | Hook | Trigger | What it does |
40
+ |------|---------|-------------|
41
+ | `risk-score.sh` | Every prompt | Injects risk scoring context |
42
+ | `secret-leak-gate.sh` | Edit or Write | Blocks writes containing secrets |
43
+ | `wip-risk-gate.sh` | Edit or Write | Blocks edits if WIP risk hasn't been assessed |
44
+ | `risk-policy-enforce-edit.sh` | Edit or Write | Blocks edits if no `RISK-POLICY.md` exists |
45
+ | `git-push-gate.sh` | Bash (git push) | Blocks direct `git push`; requires `npm run push:watch` |
46
+ | `risk-score-commit-gate.sh` | Bash (git commit) | Blocks commits when risk exceeds threshold |
47
+ | `risk-score-plan-enforce.sh` | ExitPlanMode | Ensures plans are risk-scored before execution |
48
+ | `plan-risk-guidance.sh` | EnterPlanMode | Injects risk guidance into plan mode |
49
+ | `wip-risk-mark.sh` | After edit | Records WIP risk assessment |
50
+ | `risk-score-mark.sh` | Agent completes | Marks risk review as done |
51
+ | `risk-hash-refresh.sh` | After Bash | Refreshes content hashes |
52
+ | `risk-score-reset.sh` | Session end | Cleans up risk markers |
53
+ | `risk-policy-reset-marker.sh` | Session end | Cleans up policy markers |
54
+
55
+ ## Agents
56
+
57
+ The plugin includes five specialised agents:
58
+
59
+ | Agent | Purpose |
60
+ |-------|---------|
61
+ | `wr-risk-scorer:agent` | Routes to the appropriate mode-specific agent |
62
+ | `wr-risk-scorer:wip` | Assesses cumulative risk after each edit |
63
+ | `wr-risk-scorer:pipeline` | Scores pipeline actions (commit, push, release) |
64
+ | `wr-risk-scorer:plan` | Reviews implementation plans for risk |
65
+ | `wr-risk-scorer:policy` | Validates `RISK-POLICY.md` for ISO 31000 compliance |
66
+
67
+ ## Updating and Uninstalling
68
+
69
+ ```bash
70
+ npx @windyroad/risk-scorer --update
71
+ npx @windyroad/risk-scorer --uninstall
72
+ ```
73
+
74
+ ## Licence
75
+
76
+ [MIT](../../LICENSE)
@@ -110,7 +110,16 @@ fi
110
110
 
111
111
  # Match gh pr merge. Should go via npm run release:watch instead.
112
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."
113
+ # Check if the project has a release:watch script
114
+ if [ -f "package.json" ] && python3 -c "
115
+ import json, sys
116
+ pkg = json.load(open('package.json'))
117
+ sys.exit(0 if 'release:watch' in pkg.get('scripts', {}) else 1)
118
+ " 2>/dev/null; then
119
+ 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."
120
+ else
121
+ risk_gate_deny "Direct \`gh pr merge\` is blocked (no release:watch script found). Create a release:watch npm script that: (1) finds and merges the release PR with \`gh pr merge\`, (2) waits for the CI workflow with \`gh run list\`, and (3) watches it with \`gh run watch --exit-status\`. Then run \`npm run release:watch\` to release."
122
+ fi
114
123
  exit 0
115
124
  fi
116
125
 
@@ -35,7 +35,7 @@ cat <<'EOF'
35
35
  "hookSpecificOutput": {
36
36
  "hookEventName": "PreToolUse",
37
37
  "permissionDecision": "deny",
38
- "permissionDecisionReason": "BLOCKED: Cannot edit RISK-POLICY.md directly. Run the /risk-policy skill first -- it enforces ISO 31000 compliance (reads the risk-scorer contract, discovers project context, checks for incidents, validates with you, and smoke-tests the result). Use the Skill tool with skill: \"risk-policy\"."
38
+ "permissionDecisionReason": "BLOCKED: Cannot edit RISK-POLICY.md directly. Run /wr-risk-scorer:update-policy first -- it enforces ISO 31000 compliance (reads the risk-scorer contract, discovers project context, checks for incidents, validates with you, and smoke-tests the result). Use the Skill tool with skill: \"wr-risk-scorer:update-policy\"."
39
39
  }
40
40
  }
41
41
  EOF
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bats
2
+ # Tests for git-push-gate.sh — gh pr merge block and release:watch guidance
3
+
4
+ setup() {
5
+ HOOKS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
6
+ HOOK="$HOOKS_DIR/git-push-gate.sh"
7
+
8
+ TEST_SESSION="bats-push-gate-$$-${BATS_TEST_NUMBER}"
9
+ # Ensure a clean risk dir
10
+ RDIR="${TMPDIR:-/tmp}/claude-risk-${TEST_SESSION}"
11
+ rm -rf "$RDIR"
12
+ mkdir -p "$RDIR"
13
+
14
+ # Create a temp project dir for package.json detection
15
+ TEST_PROJECT_DIR="$(mktemp -d)"
16
+ }
17
+
18
+ teardown() {
19
+ rm -rf "$RDIR"
20
+ rm -rf "$TEST_PROJECT_DIR"
21
+ }
22
+
23
+ # Helper: build a PreToolUse Bash input with a given command
24
+ build_input() {
25
+ local cmd="$1"
26
+ cat <<ENDJSON
27
+ {
28
+ "session_id": "$TEST_SESSION",
29
+ "tool_name": "Bash",
30
+ "tool_input": {
31
+ "command": "$cmd"
32
+ }
33
+ }
34
+ ENDJSON
35
+ }
36
+
37
+ @test "gh pr merge is blocked with release:watch guidance when script exists" {
38
+ # Create a package.json with release:watch
39
+ cat > "$TEST_PROJECT_DIR/package.json" <<'PKG'
40
+ { "scripts": { "release:watch": "bash scripts/release-watch.sh" } }
41
+ PKG
42
+
43
+ INPUT=$(build_input "gh pr merge 4 --merge")
44
+ run bash -c "cd '$TEST_PROJECT_DIR' && echo '$INPUT' | '$HOOK'"
45
+ [ "$status" -eq 0 ]
46
+ [[ "$output" == *"permissionDecision"* ]]
47
+ [[ "$output" == *"deny"* ]]
48
+ [[ "$output" == *"release:watch"* ]]
49
+ }
50
+
51
+ @test "gh pr merge tells agent to create release:watch when script missing" {
52
+ # Create a package.json WITHOUT release:watch
53
+ cat > "$TEST_PROJECT_DIR/package.json" <<'PKG'
54
+ { "scripts": { "test": "echo test" } }
55
+ PKG
56
+
57
+ INPUT=$(build_input "gh pr merge 4 --merge")
58
+ run bash -c "cd '$TEST_PROJECT_DIR' && echo '$INPUT' | '$HOOK'"
59
+ [ "$status" -eq 0 ]
60
+ [[ "$output" == *"permissionDecision"* ]]
61
+ [[ "$output" == *"deny"* ]]
62
+ # Should tell agent to create the script
63
+ [[ "$output" == *"no release:watch script"* ]]
64
+ [[ "$output" == *"gh pr merge"* ]]
65
+ [[ "$output" == *"gh run watch"* ]]
66
+ }
67
+
68
+ @test "gh pr merge tells agent to create release:watch when no package.json" {
69
+ local empty_dir="$(mktemp -d)"
70
+
71
+ INPUT=$(build_input "gh pr merge 4 --merge")
72
+ run bash -c "cd '$empty_dir' && echo '$INPUT' | '$HOOK'"
73
+ [ "$status" -eq 0 ]
74
+ [[ "$output" == *"permissionDecision"* ]]
75
+ [[ "$output" == *"deny"* ]]
76
+ # Should tell agent to create the script
77
+ [[ "$output" == *"no release:watch script"* ]]
78
+ [[ "$output" == *"gh pr merge"* ]]
79
+ [[ "$output" == *"gh run watch"* ]]
80
+
81
+ rm -rf "$empty_dir"
82
+ }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # Tests for risk-score-mark.sh subagent pattern matching
4
+
5
+ setup() {
6
+ SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
7
+ }
8
+
9
+ @test "pattern matches colon-style: wr-risk-scorer:pipeline" {
10
+ echo "wr-risk-scorer:pipeline" | grep -qE 'risk-scorer.pipeline'
11
+ }
12
+
13
+ @test "pattern matches colon-style: wr-risk-scorer:plan" {
14
+ echo "wr-risk-scorer:plan" | grep -qE 'risk-scorer.plan'
15
+ }
16
+
17
+ @test "pattern matches colon-style: wr-risk-scorer:wip" {
18
+ echo "wr-risk-scorer:wip" | grep -qE 'risk-scorer.wip'
19
+ }
20
+
21
+ @test "pattern matches colon-style: wr-risk-scorer:policy" {
22
+ echo "wr-risk-scorer:policy" | grep -qE 'risk-scorer.policy'
23
+ }
24
+
25
+ @test "case guard matches wr-risk-scorer:pipeline" {
26
+ SUBAGENT="wr-risk-scorer:pipeline"
27
+ case "$SUBAGENT" in
28
+ *risk-scorer*) true ;;
29
+ *) false ;;
30
+ esac
31
+ }
32
+
33
+ @test "case guard does NOT match unrelated agent" {
34
+ SUBAGENT="wr-architect:agent"
35
+ case "$SUBAGENT" in
36
+ *risk-scorer*) false ;;
37
+ *) true ;;
38
+ esac
39
+ }
@@ -17,16 +17,8 @@ SESSION_ID=$(_get_session_id)
17
17
 
18
18
  MARKER="$(_risk_dir "$SESSION_ID")/wip-reviewed"
19
19
 
20
- case "$TOOL_NAME" in
21
- Edit|Write)
22
- FILE_PATH=$(_get_file_path)
23
- [ -n "$FILE_PATH" ] || exit 0
24
-
25
- if ! _is_doc_file "$FILE_PATH"; then
26
- rm -f "$MARKER"
27
- fi
28
- ;;
29
- # Agent case handled by risk-score-mark.sh
30
- esac
20
+ # WIP marker persists after assessment — allows multiple edits.
21
+ # Cleared on session end by risk-score-reset.sh (Stop hook).
22
+ # Agent case (marker creation) handled by risk-score-mark.sh.
31
23
 
32
24
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/risk-scorer",
3
- "version": "0.1.3",
3
+ "version": "0.1.4-preview.27",
4
4
  "description": "Pipeline risk scoring, commit/push gates, and secret leak detection",
5
5
  "bin": {
6
6
  "windyroad-risk-scorer": "./bin/install.mjs"
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: wr:risk-policy
2
+ name: wr-risk-scorer:update-policy
3
3
  description: Create or update the project's RISK-POLICY.md per ISO 31000 and the risk-scorer agent. Examines the project to derive business-specific impact levels.
4
4
  allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent
5
5
  ---