@qball-inc/the-bulwark 1.2.1 → 1.3.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.
Files changed (40) hide show
  1. package/.claude-plugin/plugin.json +50 -42
  2. package/CHANGELOG.md +72 -30
  3. package/CONTRIBUTING.md +52 -0
  4. package/README.md +97 -373
  5. package/hooks/hooks.json +100 -88
  6. package/package.json +46 -46
  7. package/scripts/hooks/bulwark-permission-hook.sh +306 -0
  8. package/skills/anthropic-validator/SKILL.md +6 -0
  9. package/skills/anthropic-validator/references/skills-checklist.md +2 -1
  10. package/skills/anthropic-validator/references/skills-validation.md +2 -1
  11. package/skills/assertion-patterns/SKILL.md +3 -0
  12. package/skills/bug-magnet-data/SKILL.md +3 -0
  13. package/skills/bulwark-brainstorm/SKILL.md +8 -0
  14. package/skills/bulwark-research/SKILL.md +8 -0
  15. package/skills/bulwark-scaffold/SKILL.md +75 -2
  16. package/skills/bulwark-statusline/SKILL.md +3 -1
  17. package/skills/bulwark-verify/SKILL.md +9 -0
  18. package/skills/code-review/SKILL.md +72 -89
  19. package/skills/code-review/references/diagnostic-schema.md +119 -0
  20. package/skills/component-patterns/SKILL.md +3 -0
  21. package/skills/continuous-feedback/SKILL.md +9 -0
  22. package/skills/create-skill/SKILL.md +9 -0
  23. package/skills/create-subagent/SKILL.md +7 -0
  24. package/skills/fix-bug/SKILL.md +4 -0
  25. package/skills/governance-protocol/SKILL.md +1 -0
  26. package/skills/init/SKILL.md +6 -0
  27. package/skills/issue-debugging/SKILL.md +3 -0
  28. package/skills/mock-detection/SKILL.md +5 -0
  29. package/skills/pipeline-templates/SKILL.md +3 -0
  30. package/skills/plan-creation/SKILL.md +10 -0
  31. package/skills/plan-to-tasks/SKILL.md +8 -0
  32. package/skills/product-ideation/SKILL.md +6 -0
  33. package/skills/session-handoff/SKILL.md +4 -0
  34. package/skills/setup-lsp/SKILL.md +6 -0
  35. package/skills/spec-drift-check/SKILL.md +8 -5
  36. package/skills/subagent-output-templating/SKILL.md +2 -0
  37. package/skills/subagent-prompting/SKILL.md +2 -0
  38. package/skills/test-audit/SKILL.md +10 -0
  39. package/skills/test-classification/SKILL.md +5 -0
  40. package/skills/test-fixture-creation/SKILL.md +6 -0
package/hooks/hooks.json CHANGED
@@ -1,88 +1,100 @@
1
- {
2
- "description": "The Bulwark quality enforcement hooks - Defense-in-Depth OUTER RING",
3
- "hooks": {
4
- "PostToolUse": [
5
- {
6
- "matcher": "Write|Edit|MultiEdit",
7
- "hooks": [
8
- {
9
- "type": "command",
10
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/enforce-quality.sh",
11
- "timeout": 60
12
- }
13
- ]
14
- }
15
- ],
16
- "SubagentStart": [
17
- {
18
- "hooks": [
19
- {
20
- "type": "command",
21
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/track-pipeline-start.sh",
22
- "timeout": 30
23
- }
24
- ]
25
- }
26
- ],
27
- "SubagentStop": [
28
- {
29
- "hooks": [
30
- {
31
- "type": "command",
32
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/track-pipeline-stop.sh",
33
- "timeout": 30
34
- }
35
- ]
36
- }
37
- ],
38
- "Stop": [
39
- {
40
- "hooks": [
41
- {
42
- "type": "command",
43
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/suggest-pipeline-stop.sh",
44
- "timeout": 30
45
- }
46
- ]
47
- }
48
- ],
49
- "SessionStart": [
50
- {
51
- "hooks": [
52
- {
53
- "type": "command",
54
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/inject-protocol.sh",
55
- "timeout": 5
56
- }
57
- ]
58
- },
59
- {
60
- "hooks": [
61
- {
62
- "type": "command",
63
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/cleanup-stale.sh",
64
- "timeout": 30
65
- }
66
- ]
67
- },
68
- {
69
- "hooks": [
70
- {
71
- "type": "command",
72
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/cleanup-review-registry.sh",
73
- "timeout": 5
74
- }
75
- ]
76
- },
77
- {
78
- "hooks": [
79
- {
80
- "type": "command",
81
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/check-template-drift.sh",
82
- "timeout": 5
83
- }
84
- ]
85
- }
86
- ]
87
- }
88
- }
1
+ {
2
+ "description": "The Bulwark quality enforcement hooks - Defense-in-Depth OUTER RING",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Read|Edit|Bash",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/bulwark-permission-hook.sh",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "PostToolUse": [
17
+ {
18
+ "matcher": "Write|Edit|MultiEdit",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/enforce-quality.sh",
23
+ "timeout": 60
24
+ }
25
+ ]
26
+ }
27
+ ],
28
+ "SubagentStart": [
29
+ {
30
+ "hooks": [
31
+ {
32
+ "type": "command",
33
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/track-pipeline-start.sh",
34
+ "timeout": 30
35
+ }
36
+ ]
37
+ }
38
+ ],
39
+ "SubagentStop": [
40
+ {
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/track-pipeline-stop.sh",
45
+ "timeout": 30
46
+ }
47
+ ]
48
+ }
49
+ ],
50
+ "Stop": [
51
+ {
52
+ "hooks": [
53
+ {
54
+ "type": "command",
55
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/suggest-pipeline-stop.sh",
56
+ "timeout": 30
57
+ }
58
+ ]
59
+ }
60
+ ],
61
+ "SessionStart": [
62
+ {
63
+ "hooks": [
64
+ {
65
+ "type": "command",
66
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/inject-protocol.sh",
67
+ "timeout": 5
68
+ }
69
+ ]
70
+ },
71
+ {
72
+ "hooks": [
73
+ {
74
+ "type": "command",
75
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/cleanup-stale.sh",
76
+ "timeout": 30
77
+ }
78
+ ]
79
+ },
80
+ {
81
+ "hooks": [
82
+ {
83
+ "type": "command",
84
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/cleanup-review-registry.sh",
85
+ "timeout": 5
86
+ }
87
+ ]
88
+ },
89
+ {
90
+ "hooks": [
91
+ {
92
+ "type": "command",
93
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/check-template-drift.sh",
94
+ "timeout": 5
95
+ }
96
+ ]
97
+ }
98
+ ]
99
+ }
100
+ }
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
- {
2
- "name": "@qball-inc/the-bulwark",
3
- "version": "1.2.1",
4
- "description": "Full-lifecycle SDLC guardrailing framework for Claude Code — from product ideation and planning through implementation, code review, and test validation. Enterprise-grade skills and agents for AI-human peer collaboration.",
5
- "license": "MIT",
6
- "author": "Ashay Kubal <https://ashaykubal.com>",
7
- "homepage": "https://github.com/QBall-Inc",
8
- "repository": {
9
- "type": "git",
10
- "url": "https://github.com/QBall-Inc/the-bulwark.git"
11
- },
12
- "keywords": [
13
- "claude-code",
14
- "claude-code-plugin",
15
- "sdlc",
16
- "quality-enforcement",
17
- "code-review",
18
- "testing",
19
- "governance",
20
- "ideation",
21
- "product-ideation",
22
- "product-management",
23
- "market-research",
24
- "competitive-research",
25
- "brainstorming",
26
- "planning",
27
- "plan-creation",
28
- "agent-design",
29
- "skill-design",
30
- "test-audit",
31
- "statusline",
32
- "agent-teams"
33
- ],
34
- "scripts": {
35
- "typecheck": "tsc --noEmit",
36
- "lint": "eslint . --ext .ts"
37
- },
38
- "devDependencies": {
39
- "@types/bun": "^1.3.13",
40
- "@types/jest": "^29.5.0",
41
- "@typescript-eslint/eslint-plugin": "^6.21.0",
42
- "@typescript-eslint/parser": "^6.21.0",
43
- "eslint": "^8.56.0",
44
- "typescript": "^5.3.0"
45
- }
46
- }
1
+ {
2
+ "name": "@qball-inc/the-bulwark",
3
+ "version": "1.3.0",
4
+ "description": "Full-lifecycle SDLC guardrailing framework for Claude Code — from product ideation and planning through implementation, code review, and test validation. Enterprise-grade skills and agents for AI-human peer collaboration.",
5
+ "license": "MIT",
6
+ "author": "Ashay Kubal <https://ashaykubal.com>",
7
+ "homepage": "https://github.com/QBall-Inc",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/QBall-Inc/the-bulwark.git"
11
+ },
12
+ "keywords": [
13
+ "claude-code",
14
+ "claude-code-plugin",
15
+ "sdlc",
16
+ "quality-enforcement",
17
+ "code-review",
18
+ "testing",
19
+ "governance",
20
+ "ideation",
21
+ "product-ideation",
22
+ "product-management",
23
+ "market-research",
24
+ "competitive-research",
25
+ "brainstorming",
26
+ "planning",
27
+ "plan-creation",
28
+ "agent-design",
29
+ "skill-design",
30
+ "test-audit",
31
+ "statusline",
32
+ "agent-teams"
33
+ ],
34
+ "scripts": {
35
+ "typecheck": "tsc --noEmit",
36
+ "lint": "eslint . --ext .ts"
37
+ },
38
+ "devDependencies": {
39
+ "@types/bun": "^1.3.13",
40
+ "@types/jest": "^29.5.0",
41
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
42
+ "@typescript-eslint/parser": "^6.21.0",
43
+ "eslint": "^8.56.0",
44
+ "typescript": "^5.3.0"
45
+ }
46
+ }
@@ -0,0 +1,306 @@
1
+ #!/bin/bash
2
+ # bulwark-permission-hook.sh - PreToolUse permission-bypass for Bulwark bundled assets
3
+ #
4
+ # OPT-IN, DEFAULT OFF. Auto-approves Read/Edit/Bash operations whose target
5
+ # resolves INSIDE a Bulwark plugin root, and passes through (no opinion) for
6
+ # everything else. A scoped workaround for upstream CC permission-prompt bugs
7
+ # on plugin-bundled assets the user already trusted at install time.
8
+ #
9
+ # Design + safety model: docs/internal/p10.8-hook-design.md
10
+ # Brief: plans/task-briefs/P10.8-pretooluse-hook-permission-bypass.md
11
+ #
12
+ # Event: PreToolUse, matcher "Read|Edit|Bash"
13
+ # stdin: PreToolUse event JSON (.tool_name, .tool_input.file_path | .command)
14
+ # stdout: hookSpecificOutput JSON for allow/deny; EMPTY for pass-through (defer)
15
+ # exit: always 0 — decisions are expressed in JSON, never via exit 2
16
+ #
17
+ # Decision model (all on the CANONICAL path, never the literal string):
18
+ # - target canonically under a plugin root -> allow
19
+ # - target CLAIMS a plugin root but escapes it -> deny (anti-spoof)
20
+ # - anything else -> pass-through (defer)
21
+ # Bash is the highest-risk surface: only SIMPLE commands (no shell
22
+ # metacharacters) whose every path token sits under a plugin root are allowed.
23
+ # Write is intentionally NOT matched -> writes always keep CC's default prompt.
24
+ #
25
+ # Opt-in gate (default OFF), CONTEXT-AWARE so default-OFF never depends on CC
26
+ # exporting a userConfig default:
27
+ # - Plugin context ($CLAUDE_PLUGIN_ROOT set — installed plugin or --plugin-dir):
28
+ # require an EXPLICIT $CLAUDE_PLUGIN_OPTION_ENABLE_PERMISSION_BYPASS=true;
29
+ # unset / "false" / anything-else -> inert (fail-safe OFF). The plugin's
30
+ # userConfig.enable_permission_bypass (default false) drives this — and even
31
+ # if CC omits the var for an unchanged default, the hook stays OFF.
32
+ # - Project/scaffold context ($CLAUDE_PLUGIN_ROOT unset): the hook's presence
33
+ # in the project's .claude/settings.json IS the opt-in, so an UNSET var is
34
+ # active; an explicit "false" still disables it.
35
+ #
36
+ # Portability: prefers GNU `realpath -m`; falls back to a pure-bash lexical
37
+ # `..`/`.` normalizer (no filesystem access) so behavior is identical on
38
+ # WSL/Linux/macOS. Canonicalization is fail-safe: a path it cannot resolve
39
+ # to an absolute form never matches a root (so it is never auto-approved).
40
+ #
41
+ # Usage: invoked by Claude Code as a PreToolUse hook; not run directly.
42
+
43
+ set -euo pipefail
44
+
45
+ # ─────────────────────────────────────────────────────────────────────────────
46
+ # Opt-in gate (default OFF) — context-aware (see header)
47
+ # ─────────────────────────────────────────────────────────────────────────────
48
+ GATE="${CLAUDE_PLUGIN_OPTION_ENABLE_PERMISSION_BYPASS-__unset__}"
49
+ if [ -n "${CLAUDE_PLUGIN_ROOT-}" ]; then
50
+ # Plugin context: default-OFF must NOT depend on CC exporting the userConfig
51
+ # default, so require an EXPLICIT opt-in. unset/false/unknown -> inert.
52
+ case "$GATE" in
53
+ true|1|yes|on) : ;;
54
+ *) exit 0 ;;
55
+ esac
56
+ else
57
+ # Project/scaffold context: presence in .claude/settings.json IS the opt-in,
58
+ # so an UNSET gate is active; an explicit "false" still disables.
59
+ case "$GATE" in
60
+ __unset__|true|1|yes|on) : ;;
61
+ *) exit 0 ;;
62
+ esac
63
+ fi
64
+
65
+ # ─────────────────────────────────────────────────────────────────────────────
66
+ # Decision emitters
67
+ # ─────────────────────────────────────────────────────────────────────────────
68
+ emit_allow() {
69
+ printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"%s"}}\n' "$1"
70
+ exit 0
71
+ }
72
+
73
+ emit_deny() {
74
+ log_deny "$1" "${2:-}"
75
+ printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "$1"
76
+ exit 0
77
+ }
78
+
79
+ # Deny is rare and security-relevant -> log it (allows/pass-throughs are hot-path,
80
+ # not logged). Fully non-fatal; only appends if a logs/ dir already exists.
81
+ log_deny() {
82
+ local reason="$1" detail="$2"
83
+ local logdir="${CLAUDE_PROJECT_DIR:-.}/logs"
84
+ [ -d "$logdir" ] || return 0
85
+ # Sanitize the user-influenced detail before appending to the shared log:
86
+ # strip CR/LF (prevents log-line spoofing) and bound length (SEC-SUG-1).
87
+ detail="${detail//$'\n'/ }"
88
+ detail="${detail//$'\r'/ }"
89
+ detail="${detail:0:500}"
90
+ printf '[%s] PreToolUse bulwark-permission-hook: DENY %s (%s)\n' \
91
+ "$(date -Iseconds 2>/dev/null || echo now)" "$reason" "$detail" \
92
+ >> "$logdir/hooks.log" 2>/dev/null || true
93
+ }
94
+
95
+ # ─────────────────────────────────────────────────────────────────────────────
96
+ # Path expansion + canonicalization
97
+ # ─────────────────────────────────────────────────────────────────────────────
98
+
99
+ # Expand a leading ~ and any literal $HOME / $CLAUDE_PLUGIN_ROOT tokens the
100
+ # Bash command string may carry unexpanded.
101
+ expand_path() {
102
+ local p="$1"
103
+ # Match a LITERAL leading tilde in the input and expand it ourselves; tildes
104
+ # must NOT shell-expand inside these case patterns (SC2088 here is intentional).
105
+ # shellcheck disable=SC2088
106
+ case "$p" in
107
+ "~") p="${HOME:-}" ;;
108
+ "~/"*) p="${HOME:-}/${p#"~/"}" ;;
109
+ esac
110
+ p="${p//\$\{CLAUDE_PLUGIN_ROOT\}/${CLAUDE_PLUGIN_ROOT:-}}"
111
+ p="${p//\$CLAUDE_PLUGIN_ROOT/${CLAUDE_PLUGIN_ROOT:-}}"
112
+ p="${p//\$\{HOME\}/${HOME:-}}"
113
+ p="${p//\$HOME/${HOME:-}}"
114
+ printf '%s' "$p"
115
+ }
116
+
117
+ # Pure-bash lexical resolution of . and .. — no filesystem access, portable.
118
+ # Only normalizes ABSOLUTE paths; a relative path is returned unchanged so it
119
+ # can never match an absolute root (fail-safe).
120
+ lexical_normalize() {
121
+ local path="$1"
122
+ case "$path" in
123
+ /*) ;;
124
+ *) printf '%s' "$path"; return 0 ;;
125
+ esac
126
+ local IFS='/' seg
127
+ local -a parts=() out=()
128
+ read -ra parts <<< "$path" || true
129
+ for seg in "${parts[@]}"; do
130
+ case "$seg" in
131
+ ''|'.') ;; # drop empty + current-dir
132
+ '..') [ "${#out[@]}" -gt 0 ] && unset 'out[${#out[@]}-1]' && out=("${out[@]}") ;;
133
+ *) out+=("$seg") ;;
134
+ esac
135
+ done
136
+ local result=""
137
+ for seg in "${out[@]}"; do result="$result/$seg"; done
138
+ [ -n "$result" ] || result="/"
139
+ printf '%s' "$result"
140
+ }
141
+
142
+ # Canonicalize an absolute path (resolve .. and, when realpath is available,
143
+ # symlinks). Relative paths are returned unchanged (never match a root).
144
+ canonicalize() {
145
+ local p="$1" out
146
+ case "$p" in
147
+ /*) ;;
148
+ *) printf '%s' "$p"; return 0 ;;
149
+ esac
150
+ if out=$(realpath -m -- "$p" 2>/dev/null) && [ -n "$out" ]; then
151
+ printf '%s' "$out"; return 0
152
+ fi
153
+ lexical_normalize "$p"
154
+ }
155
+
156
+ # ─────────────────────────────────────────────────────────────────────────────
157
+ # Root membership (operate on canonical paths only)
158
+ # ─────────────────────────────────────────────────────────────────────────────
159
+
160
+ # True if canonical path lives under ~/.claude/plugins/cache/<owner>/the-bulwark[-*]/...
161
+ # Version-agnostic by design (#15642): the version segment is below the plugin dir.
162
+ is_under_cache_root() {
163
+ local cp="$1" suffix owner rest plugin
164
+ case "$cp" in
165
+ *"/.claude/plugins/cache/"*) ;;
166
+ *) return 1 ;;
167
+ esac
168
+ suffix="${cp#*"/.claude/plugins/cache/"}" # owner/plugin/version/...
169
+ owner="${suffix%%/*}"
170
+ [ -n "$owner" ] && [ "$owner" != "$suffix" ] || return 1
171
+ rest="${suffix#*/}"
172
+ plugin="${rest%%/*}"
173
+ case "$plugin" in
174
+ the-bulwark|the-bulwark-*) return 0 ;;
175
+ esac
176
+ return 1
177
+ }
178
+
179
+ # True if canonical path lives under the dev plugin root ($CLAUDE_PLUGIN_ROOT, for --plugin-dir).
180
+ is_under_dev_root() {
181
+ local cp="$1" droot
182
+ [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] || return 1
183
+ droot="$(canonicalize "$CLAUDE_PLUGIN_ROOT")"
184
+ [ -n "$droot" ] || return 1
185
+ case "$cp" in
186
+ "$droot"|"$droot"/*) return 0 ;;
187
+ esac
188
+ return 1
189
+ }
190
+
191
+ is_under_root() {
192
+ is_under_cache_root "$1" || is_under_dev_root "$1"
193
+ }
194
+
195
+ # True if the RAW (pre-canonical) string references a plugin marker — used to
196
+ # tell a traversal-spoof (deny) apart from an unrelated path (pass-through).
197
+ claims_root() {
198
+ local raw="$1"
199
+ case "$raw" in
200
+ *".claude/plugins/cache/"*"the-bulwark"*) return 0 ;;
201
+ esac
202
+ # Match the literal, unexpanded $CLAUDE_PLUGIN_ROOT token a raw command may
203
+ # carry; single quotes are intentional (SC2016 expected — we match literals).
204
+ # shellcheck disable=SC2016
205
+ case "$raw" in
206
+ *'${CLAUDE_PLUGIN_ROOT}'*|*'$CLAUDE_PLUGIN_ROOT'*) return 0 ;;
207
+ esac
208
+ if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
209
+ case "$raw" in
210
+ *"$CLAUDE_PLUGIN_ROOT"*) return 0 ;;
211
+ esac
212
+ fi
213
+ return 1
214
+ }
215
+
216
+ # ─────────────────────────────────────────────────────────────────────────────
217
+ # Decision logic
218
+ # ─────────────────────────────────────────────────────────────────────────────
219
+
220
+ # Read / Edit: decide on the single file path.
221
+ decide_path() {
222
+ local raw="$1" canon
223
+ canon="$(canonicalize "$(expand_path "$raw")")"
224
+ if [ -n "$canon" ] && is_under_root "$canon"; then
225
+ emit_allow "bulwark-bundled-asset"
226
+ elif claims_root "$raw"; then
227
+ emit_deny "path escapes the Bulwark plugin root" "$raw"
228
+ fi
229
+ exit 0 # pass-through
230
+ }
231
+
232
+ # Bash: most conservative. Reject anything we cannot reason about simply, then
233
+ # auto-approve ONLY a "run a plugin script" shape: the command verb is itself a
234
+ # plugin-script path (direct exec) OR a bare bash/sh interpreter, AND every
235
+ # path-like token sits under a plugin root. A non-script verb on a plugin path
236
+ # (e.g. `rm <plugin-file>`) is NOT auto-approved — it falls through to CC's
237
+ # default prompt (CR-SUG-1).
238
+ decide_bash() {
239
+ local cmd="$1" expanded tok canon verb
240
+ # Literal shell metacharacters; single quotes are intentional (SC2016 expected).
241
+ # shellcheck disable=SC2016
242
+ case "$cmd" in
243
+ *'&&'*|*'||'*|*';'*|*'|'*|*'$('*|*'`'*|*'>'*|*'<'*|*'&'*|*$'\n'*)
244
+ exit 0 ;; # compound / pipe / subshell / redirect / background -> pass-through
245
+ esac
246
+ expanded="$(expand_path "$cmd")"
247
+ # Word-split WITHOUT globbing (read -ra, default IFS) so a literal '*' in the
248
+ # command is never pathname-expanded during inspection. A path token containing
249
+ # spaces won't match a root cleanly -> falls through to pass-through (SEC-SUG-3).
250
+ local -a toks=()
251
+ read -ra toks <<< "$expanded" || true
252
+ [ "${#toks[@]}" -gt 0 ] || exit 0
253
+ # Command verb must be a plugin script (direct exec) or a bare bash/sh interpreter.
254
+ verb="${toks[0]}"
255
+ local verb_ok=0
256
+ case "$verb" in
257
+ bash|sh) verb_ok=1 ;;
258
+ */*)
259
+ canon="$(canonicalize "$verb")"
260
+ if [ -n "$canon" ] && is_under_root "$canon"; then verb_ok=1; fi
261
+ ;;
262
+ esac
263
+ local has_plugin_token=0 has_escape=0 has_external=0
264
+ for tok in "${toks[@]}"; do
265
+ case "$tok" in
266
+ */*) ;; # path-like token
267
+ *) continue ;; # flags / bare words -> ignore
268
+ esac
269
+ canon="$(canonicalize "$tok")"
270
+ if [ -n "$canon" ] && is_under_root "$canon"; then
271
+ has_plugin_token=1
272
+ elif claims_root "$tok"; then
273
+ has_escape=1
274
+ else
275
+ has_external=1
276
+ fi
277
+ done
278
+ if [ "$has_escape" -eq 1 ]; then
279
+ emit_deny "command path escapes the Bulwark plugin root" "$cmd"
280
+ elif [ "$verb_ok" -eq 1 ] && [ "$has_plugin_token" -eq 1 ] && [ "$has_external" -eq 0 ]; then
281
+ emit_allow "bulwark-bundled-script"
282
+ fi
283
+ exit 0 # pass-through
284
+ }
285
+
286
+ # ─────────────────────────────────────────────────────────────────────────────
287
+ # Main
288
+ # ─────────────────────────────────────────────────────────────────────────────
289
+ INPUT="$(cat)"
290
+ TOOL="$(printf '%s' "$INPUT" | jq -r '.tool_name // ""')"
291
+
292
+ case "$TOOL" in
293
+ Read|Edit)
294
+ FP="$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // ""')"
295
+ [ -n "$FP" ] || exit 0
296
+ decide_path "$FP"
297
+ ;;
298
+ Bash)
299
+ CMD="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')"
300
+ [ -n "$CMD" ] || exit 0
301
+ decide_bash "$CMD"
302
+ ;;
303
+ *)
304
+ exit 0 # not a tool this hook decides on
305
+ ;;
306
+ esac
@@ -3,6 +3,12 @@ name: anthropic-validator
3
3
  description: Validates Claude Code assets (skills, hooks, agents, commands, MCP servers, plugins) against official Anthropic standards. Fetches latest docs dynamically and produces structured validation reports.
4
4
  when_to_use: When validating Claude Code assets (skills, hooks, agents, commands, MCP servers, plugins) against official Anthropic standards before release, after creation, or when auditing for spec compliance.
5
5
  user-invocable: true
6
+ allowed-tools:
7
+ - AskUserQuestion
8
+ - Glob
9
+ - Read
10
+ - Task
11
+ - Write
6
12
  version: 1.1.1
7
13
  author: "Ashay Kubal @ Qball Inc."
8
14
  ---
@@ -21,7 +21,8 @@ All skill frontmatter fields are technically optional per the Anthropic spec. `d
21
21
  | `arguments` | string or YAML list | Argument schema (space-separated string or list) |
22
22
  | `disable-model-invocation` | boolean | `true` blocks auto-invocation but keeps `/skill-name` working |
23
23
  | `user-invocable` | boolean | `true` to show in `/` menu, `false` to hide |
24
- | `allowed-tools` | string or YAML list | **Canonical tool restriction field for SKILLS** (NOT `tools`) |
24
+ | `allowed-tools` | string or YAML list | **Pre-authorizes** listed tools (skips approval prompts); does NOT restrict tool availability (NOT `tools`) |
25
+ | `disallowed-tools` | string or YAML list | **Restriction field for SKILLS**: removes tools from the available pool while the skill is active (clears on next message) |
25
26
  | `model` | string | `haiku`, `sonnet`, `opus` |
26
27
  | `agent` | string | Subagent name to delegate to |
27
28
  | `effort` | enum | `low`, `medium`, `high`, `xhigh`, `max` |
@@ -47,7 +47,8 @@ Per-asset-type workflow + validation points for **skills** (`SKILL.md` files). L
47
47
  | `arguments` | space-separated string OR YAML list | Optional — argument schema |
48
48
  | `disable-model-invocation` | boolean | Optional — `true` blocks auto-invocation but keeps `/` invocation working |
49
49
  | `user-invocable` | boolean | Optional — controls `/` menu visibility |
50
- | `allowed-tools` | space-separated string OR YAML list | Optional — **canonical tool restriction field for SKILLS** (NOT `tools`; that field is for AGENTS) |
50
+ | `allowed-tools` | space-separated string OR YAML list | Optional — **pre-authorizes** the listed tools (skips approval prompts while the skill is active); does NOT restrict which tools are available (NOT `tools`; that field is for AGENTS) |
51
+ | `disallowed-tools` | space-separated/comma string OR YAML list | Optional — **the actual tool-restriction field for SKILLS**: removes the listed tools from Claude's available pool while the skill is active (restriction clears on the next user message) |
51
52
  | `model` | string | Optional — `haiku`, `sonnet`, `opus` |
52
53
  | `agent` | string (subagent name) | Optional — delegate to a named subagent |
53
54
  | `effort` | enum: `low`, `medium`, `high`, `xhigh`, `max` | Optional — model effort level |
@@ -2,6 +2,9 @@
2
2
  name: assertion-patterns
3
3
  description: Real output verification vs mock calls. Use when transforming T1-T4 violating tests to verify observable behavior.
4
4
  user-invocable: false
5
+ allowed-tools:
6
+ - Read
7
+ - Write
5
8
  version: 1.0.0
6
9
  author: "Ashay Kubal @ Qball Inc."
7
10
  ---
@@ -2,6 +2,9 @@
2
2
  name: bug-magnet-data
3
3
  description: Curated edge case test data for boundary testing, verification scripts, and test generation. Provides pre-curated reference data organized by data type with context-specific loading guidance.
4
4
  user-invocable: false
5
+ allowed-tools:
6
+ - Read
7
+ - Write
5
8
  version: 1.0.0
6
9
  author: "Ashay Kubal @ Qball Inc."
7
10
  ---
@@ -5,6 +5,14 @@ user-invocable: true
5
5
  argument-hint: "<topic, filepath, or directory> [--research <synthesis-file>] [--scoped | --exploratory]"
6
6
  skills:
7
7
  - subagent-prompting
8
+ allowed-tools:
9
+ - AskUserQuestion
10
+ - Bash
11
+ - Glob
12
+ - Read
13
+ - Skill
14
+ - Task
15
+ - Write
8
16
  version: 1.0.1
9
17
  author: "Ashay Kubal @ Qball Inc."
10
18
  ---