@windyroad/risk-scorer 0.1.1 → 0.1.2
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/bin/install.mjs +2 -1
- package/hooks/git-push-gate.sh +3 -3
- package/hooks/lib/risk-gate.sh +2 -2
- package/hooks/risk-score-commit-gate.sh +1 -1
- package/hooks/risk-score-mark.sh +5 -5
- package/hooks/risk-score-plan-enforce.sh +1 -1
- package/hooks/wip-risk-gate.sh +1 -1
- package/lib/install-utils.mjs +19 -39
- package/package.json +1 -1
package/bin/install.mjs
CHANGED
|
@@ -20,6 +20,7 @@ Pipeline risk scoring, commit/push gates, and secret leak detection
|
|
|
20
20
|
Options:
|
|
21
21
|
--update Update this plugin and its skills
|
|
22
22
|
--uninstall Remove this plugin
|
|
23
|
+
--scope Installation scope: project (default) or user
|
|
23
24
|
--dry-run Show what would be done without executing
|
|
24
25
|
--help, -h Show this help
|
|
25
26
|
`);
|
|
@@ -38,5 +39,5 @@ if (flags.uninstall) {
|
|
|
38
39
|
} else if (flags.update) {
|
|
39
40
|
utils.updatePackage(PLUGIN);
|
|
40
41
|
} else {
|
|
41
|
-
utils.installPackage(PLUGIN, { deps: DEPS });
|
|
42
|
+
utils.installPackage(PLUGIN, { deps: DEPS, scope: flags.scope });
|
|
42
43
|
}
|
package/hooks/git-push-gate.sh
CHANGED
|
@@ -47,7 +47,7 @@ if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*npm run push:watch(\s|$)'; then
|
|
|
47
47
|
fi
|
|
48
48
|
PUSH_SCORE_FILE="${RDIR}/push"
|
|
49
49
|
if [ ! -f "$PUSH_SCORE_FILE" ]; then
|
|
50
|
-
risk_gate_deny "Push blocked: No push risk score found. Delegate to risk-scorer
|
|
50
|
+
risk_gate_deny "Push blocked: No push risk score found. Delegate to wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') to assess cumulative pipeline risk."
|
|
51
51
|
exit 0
|
|
52
52
|
fi
|
|
53
53
|
PUSH_NOW=$(date +%s)
|
|
@@ -65,7 +65,7 @@ if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*npm run push:watch(\s|$)'; then
|
|
|
65
65
|
fi
|
|
66
66
|
PUSH_DENIED=$(python3 -c "print('yes' if float('${PUSH_SCORE}') >= 5 else 'no')" 2>/dev/null || echo "no")
|
|
67
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
|
|
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 wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') — it will create a bypass marker."
|
|
69
69
|
exit 0
|
|
70
70
|
fi
|
|
71
71
|
fi
|
|
@@ -101,7 +101,7 @@ if echo "$COMMAND" | grep -qE '(^|;|&&|\|\|)\s*npm run release:watch(\s|$)'; the
|
|
|
101
101
|
exit 0
|
|
102
102
|
fi
|
|
103
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
|
|
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 wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') with incident context for an incident bypass."
|
|
105
105
|
exit 0
|
|
106
106
|
fi
|
|
107
107
|
fi
|
package/hooks/lib/risk-gate.sh
CHANGED
|
@@ -21,7 +21,7 @@ check_risk_gate() {
|
|
|
21
21
|
|
|
22
22
|
# 1. Score file must exist (fail-closed)
|
|
23
23
|
if [ ! -f "$SCORE_FILE" ]; then
|
|
24
|
-
RISK_GATE_REASON="No ${ACTION} risk score found.
|
|
24
|
+
RISK_GATE_REASON="No ${ACTION} risk score found. Delegate to wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') to assess cumulative pipeline risk."
|
|
25
25
|
return 1
|
|
26
26
|
fi
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ check_risk_gate() {
|
|
|
30
30
|
local SCORE_TIME=$(_mtime "$SCORE_FILE")
|
|
31
31
|
local AGE=$(( NOW - SCORE_TIME ))
|
|
32
32
|
if [ "$AGE" -ge "$TTL_SECONDS" ]; then
|
|
33
|
-
RISK_GATE_REASON="Risk score expired (${AGE}s old, TTL ${TTL_SECONDS}s).
|
|
33
|
+
RISK_GATE_REASON="Risk score expired (${AGE}s old, TTL ${TTL_SECONDS}s). Delegate to wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') to rescore."
|
|
34
34
|
return 1
|
|
35
35
|
fi
|
|
36
36
|
|
|
@@ -57,7 +57,7 @@ fi
|
|
|
57
57
|
|
|
58
58
|
# Gate check: existence, TTL, drift, threshold
|
|
59
59
|
if ! check_risk_gate "$SESSION_ID" "commit"; then
|
|
60
|
-
risk_gate_deny "Commit blocked: ${RISK_GATE_REASON} To proceed: (1) stage files with git add, (2) delegate to risk-scorer
|
|
60
|
+
risk_gate_deny "Commit blocked: ${RISK_GATE_REASON} To proceed: (1) stage files with git add, (2) delegate to wr-risk-scorer:pipeline (subagent_type: 'wr-risk-scorer:pipeline') to assess cumulative pipeline risk. If the commit is risk-neutral or risk-reducing, the scorer will create a bypass marker."
|
|
61
61
|
exit 0
|
|
62
62
|
fi
|
|
63
63
|
|
package/hooks/risk-score-mark.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# risk-scorer agents. This is the ONLY place score files are written —
|
|
5
5
|
# agents output structured markers, this hook writes the files.
|
|
6
6
|
#
|
|
7
|
-
# Handles: risk-scorer
|
|
7
|
+
# Handles: wr-risk-scorer:pipeline, wr-risk-scorer:plan, wr-risk-scorer:wip, wr-risk-scorer:policy
|
|
8
8
|
# Replaces: risk-policy-mark-reviewed.sh (which had fragile P001 backup parsing)
|
|
9
9
|
|
|
10
10
|
set -euo pipefail
|
|
@@ -34,7 +34,7 @@ RDIR=$(_risk_dir "$SESSION_ID")
|
|
|
34
34
|
# ---------------------------------------------------------------------------
|
|
35
35
|
# Pipeline scorer: write commit/push/release scores + bypass markers
|
|
36
36
|
# ---------------------------------------------------------------------------
|
|
37
|
-
if echo "$SUBAGENT" | grep -qE 'risk-scorer
|
|
37
|
+
if echo "$SUBAGENT" | grep -qE 'risk-scorer.pipeline'; then
|
|
38
38
|
# Parse RISK_SCORES: commit=N push=N release=N
|
|
39
39
|
SCORES_LINE=$(echo "$AGENT_OUTPUT" | grep -E '^RISK_SCORES:' | tail -1) || true
|
|
40
40
|
if [ -n "$SCORES_LINE" ]; then
|
|
@@ -79,7 +79,7 @@ fi
|
|
|
79
79
|
# ---------------------------------------------------------------------------
|
|
80
80
|
# Plan scorer: write plan-reviewed marker on PASS
|
|
81
81
|
# ---------------------------------------------------------------------------
|
|
82
|
-
if echo "$SUBAGENT" | grep -qE 'risk-scorer
|
|
82
|
+
if echo "$SUBAGENT" | grep -qE 'risk-scorer.plan'; then
|
|
83
83
|
VERDICT_LINE=$(echo "$AGENT_OUTPUT" | grep -E '^RISK_VERDICT:' | tail -1) || true
|
|
84
84
|
VERDICT=$(echo "$VERDICT_LINE" | sed 's/^RISK_VERDICT:[[:space:]]*//' | tr -d '[:space:]')
|
|
85
85
|
case "$VERDICT" in
|
|
@@ -98,7 +98,7 @@ fi
|
|
|
98
98
|
# ---------------------------------------------------------------------------
|
|
99
99
|
# WIP scorer: write wip-reviewed marker (unblocks next edit)
|
|
100
100
|
# ---------------------------------------------------------------------------
|
|
101
|
-
if echo "$SUBAGENT" | grep -qE 'risk-scorer
|
|
101
|
+
if echo "$SUBAGENT" | grep -qE 'risk-scorer.wip'; then
|
|
102
102
|
# WIP assessment was done — unblock next edit regardless of CONTINUE/PAUSE
|
|
103
103
|
# (PAUSE is advisory guidance to the user, not a hard gate)
|
|
104
104
|
touch "${RDIR}/wip-reviewed"
|
|
@@ -107,7 +107,7 @@ fi
|
|
|
107
107
|
# ---------------------------------------------------------------------------
|
|
108
108
|
# Policy scorer: write policy-reviewed marker on PASS
|
|
109
109
|
# ---------------------------------------------------------------------------
|
|
110
|
-
if echo "$SUBAGENT" | grep -qE 'risk-scorer
|
|
110
|
+
if echo "$SUBAGENT" | grep -qE 'risk-scorer.policy'; then
|
|
111
111
|
VERDICT_LINE=$(echo "$AGENT_OUTPUT" | grep -E '^RISK_VERDICT:' | tail -1) || true
|
|
112
112
|
VERDICT=$(echo "$VERDICT_LINE" | sed 's/^RISK_VERDICT:[[:space:]]*//' | tr -d '[:space:]')
|
|
113
113
|
case "$VERDICT" in
|
|
@@ -24,7 +24,7 @@ cat <<'EOF'
|
|
|
24
24
|
"hookSpecificOutput": {
|
|
25
25
|
"hookEventName": "PreToolUse",
|
|
26
26
|
"permissionDecision": "deny",
|
|
27
|
-
"permissionDecisionReason": "BLOCKED: Risk-scorer must review the plan before exiting plan mode. Delegate to risk-scorer
|
|
27
|
+
"permissionDecisionReason": "BLOCKED: Risk-scorer must review the plan before exiting plan mode. Delegate to wr-risk-scorer:plan (subagent_type: 'wr-risk-scorer:plan') to review the plan file for risk, including projected release risk."
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
EOF
|
package/hooks/wip-risk-gate.sh
CHANGED
|
@@ -37,7 +37,7 @@ cat <<'EOF'
|
|
|
37
37
|
"hookSpecificOutput": {
|
|
38
38
|
"hookEventName": "PreToolUse",
|
|
39
39
|
"permissionDecision": "deny",
|
|
40
|
-
"permissionDecisionReason": "WIP risk assessment required. Delegate to risk-scorer
|
|
40
|
+
"permissionDecisionReason": "WIP risk assessment required. Delegate to wr-risk-scorer:wip (subagent_type: 'wr-risk-scorer:wip') to assess cumulative pipeline risk for changes so far."
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
EOF
|
package/lib/install-utils.mjs
CHANGED
|
@@ -46,15 +46,6 @@ export function checkPrerequisites() {
|
|
|
46
46
|
);
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
execSync("npx --version", { stdio: "pipe" });
|
|
52
|
-
} catch {
|
|
53
|
-
console.error(
|
|
54
|
-
"Error: 'npx' not found. Install Node.js first:\n https://nodejs.org\n"
|
|
55
|
-
);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
49
|
}
|
|
59
50
|
|
|
60
51
|
export function addMarketplace() {
|
|
@@ -64,9 +55,9 @@ export function addMarketplace() {
|
|
|
64
55
|
);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
|
-
export function installPlugin(pluginName) {
|
|
58
|
+
export function installPlugin(pluginName, { scope = "project" } = {}) {
|
|
68
59
|
return run(
|
|
69
|
-
`claude plugin install ${pluginName}@${MARKETPLACE_NAME}`,
|
|
60
|
+
`claude plugin install ${pluginName}@${MARKETPLACE_NAME} --scope ${scope}`,
|
|
70
61
|
pluginName
|
|
71
62
|
);
|
|
72
63
|
}
|
|
@@ -79,33 +70,14 @@ export function uninstallPlugin(pluginName) {
|
|
|
79
70
|
return run(`claude plugin uninstall ${pluginName}`, `Removing ${pluginName}`);
|
|
80
71
|
}
|
|
81
72
|
|
|
82
|
-
export function installSkills() {
|
|
83
|
-
return run(
|
|
84
|
-
`npx -y skills add --yes --all ${MARKETPLACE_REPO}`,
|
|
85
|
-
"Skills (via skills package)"
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function updateSkills() {
|
|
90
|
-
return run("npx -y skills update", "Skills update");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function removeSkills() {
|
|
94
|
-
return run(
|
|
95
|
-
`npx -y skills remove --yes --all ${MARKETPLACE_REPO}`,
|
|
96
|
-
"Removing skills"
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
73
|
/**
|
|
101
|
-
* Install a single package: marketplace add + plugin install
|
|
74
|
+
* Install a single package: marketplace add + plugin install.
|
|
102
75
|
*/
|
|
103
|
-
export function installPackage(pluginName, { deps = [] } = {}) {
|
|
104
|
-
console.log(`\nInstalling @windyroad/${pluginName.replace("wr-", "")}...\n`);
|
|
76
|
+
export function installPackage(pluginName, { deps = [], scope = "project" } = {}) {
|
|
77
|
+
console.log(`\nInstalling @windyroad/${pluginName.replace("wr-", "")} (${scope} scope)...\n`);
|
|
105
78
|
|
|
106
79
|
addMarketplace();
|
|
107
|
-
installPlugin(pluginName);
|
|
108
|
-
installSkills();
|
|
80
|
+
installPlugin(pluginName, { scope });
|
|
109
81
|
|
|
110
82
|
if (deps.length > 0) {
|
|
111
83
|
console.log(`\nNote: This plugin works best with:`);
|
|
@@ -130,7 +102,6 @@ export function updatePackage(pluginName) {
|
|
|
130
102
|
"Updating marketplace"
|
|
131
103
|
);
|
|
132
104
|
updatePlugin(pluginName);
|
|
133
|
-
updateSkills();
|
|
134
105
|
|
|
135
106
|
console.log("\nDone! Restart Claude Code to apply updates.\n");
|
|
136
107
|
}
|
|
@@ -144,9 +115,6 @@ export function uninstallPackage(pluginName) {
|
|
|
144
115
|
uninstallPlugin(pluginName);
|
|
145
116
|
|
|
146
117
|
console.log("\nDone. Restart Claude Code to apply changes.\n");
|
|
147
|
-
console.log("Note: Skills are shared across packages. Run");
|
|
148
|
-
console.log(" npx @windyroad/agent-plugins --uninstall");
|
|
149
|
-
console.log("to remove all skills.\n");
|
|
150
118
|
}
|
|
151
119
|
|
|
152
120
|
/**
|
|
@@ -154,10 +122,22 @@ export function uninstallPackage(pluginName) {
|
|
|
154
122
|
*/
|
|
155
123
|
export function parseStandardArgs(argv) {
|
|
156
124
|
const args = argv.slice(2);
|
|
157
|
-
|
|
125
|
+
const flags = {
|
|
158
126
|
help: args.includes("--help") || args.includes("-h"),
|
|
159
127
|
uninstall: args.includes("--uninstall"),
|
|
160
128
|
update: args.includes("--update"),
|
|
161
129
|
dryRun: args.includes("--dry-run"),
|
|
130
|
+
scope: "project",
|
|
162
131
|
};
|
|
132
|
+
const scopeIdx = args.indexOf("--scope");
|
|
133
|
+
if (scopeIdx !== -1 && args[scopeIdx + 1]) {
|
|
134
|
+
const val = args[scopeIdx + 1];
|
|
135
|
+
if (["project", "user", "local"].includes(val)) {
|
|
136
|
+
flags.scope = val;
|
|
137
|
+
} else {
|
|
138
|
+
console.error("--scope requires: project, user, or local");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return flags;
|
|
163
143
|
}
|