@qball-inc/the-bulwark 1.0.1 → 1.1.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "the-bulwark",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
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
5
  "author": {
6
6
  "name": "Ashay Kubal",
@@ -38,6 +38,5 @@
38
38
  "test-coverage",
39
39
  "statusline",
40
40
  "agent-teams"
41
- ],
42
- "hooks": "./hooks/hooks.json"
41
+ ]
43
42
  }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ashay Kubal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -15,7 +15,8 @@
15
15
  <a href="#how-it-works">How it works</a> &middot;
16
16
  <a href="#hooks">Hooks</a> &middot;
17
17
  <a href="#skill-registry">Skills</a> &middot;
18
- <a href="#agent-registry">Agents</a>
18
+ <a href="#agent-registry">Agents</a> &middot;
19
+ <a href="#planned-enhancements">Roadmap</a>
19
20
  </p>
20
21
 
21
22
  <p align="center">
@@ -92,6 +93,8 @@ After installing, restart your Claude Code session and run the init skill:
92
93
 
93
94
  This walks you through a guided setup: Rules.md injection, CLAUDE.md configuration, and optional tooling (LSP, Justfile scaffolding, statusline). It auto-detects brownfield projects and adjusts accordingly.
94
95
 
96
+ > Having trouble installing? See [FAQ and troubleshooting](#faq-and-troubleshooting). If your issue isn't covered, please [open an issue](https://github.com/QBall-Inc/the-bulwark/issues).
97
+
95
98
  ## Prerequisites
96
99
 
97
100
  | Requirement | Details |
@@ -297,6 +300,23 @@ Agents are single-purpose sub-agents spawned by skills via the Task tool. You do
297
300
 
298
301
  ## FAQ and troubleshooting
299
302
 
303
+ ### Plugin clone fails with "Permission denied (publickey)"
304
+
305
+ If you see this error when installing from the marketplace:
306
+
307
+ ```
308
+ git@github.com: Permission denied (publickey).
309
+ fatal: Could not read from remote repository.
310
+ ```
311
+
312
+ Your git is defaulting to SSH for GitHub, but you don't have SSH keys configured. Fix by telling git to use HTTPS:
313
+
314
+ ```bash
315
+ git config --global url."https://github.com/".insteadOf "git@github.com:"
316
+ ```
317
+
318
+ Then retry the install. This applies globally and redirects all GitHub SSH URLs to HTTPS.
319
+
300
320
  ### Hooks aren't firing after install
301
321
 
302
322
  Restart your Claude Code session. Hooks only load at session start. If they still don't fire, check that the plugin is installed:
@@ -357,6 +377,26 @@ You can't disable individual plugin hooks without modifying `hooks/hooks.json` i
357
377
 
358
378
  ---
359
379
 
380
+ ## Planned enhancements
381
+
382
+ These are on the roadmap. No timeline commitments, but they represent the direction The Bulwark is heading.
383
+
384
+ **Evaluation framework.** Skills and agents are the new code layer in agentic development. They need the same rigor as code: versioned, tested, measured. We're building two new skills — `create-eval` and `run-eval` — that generate and execute evaluations for any Claude Code asset. Define test prompts, expected outputs, and grading criteria. Run them across skill versions to catch regressions. Measure conversational invocation success, checklist compliance, and output quality with structured grading reports.
385
+
386
+ **Asset baselines.** Once the eval skills exist, we'll baseline all 28 skills and 15 agents with versioned evaluations. Every asset gets a `version` field in its frontmatter and a set of evals that serve as regression references. Future changes get measured against these baselines automatically.
387
+
388
+ **Enterprise traceability.** Enhanced logging with version stamps (skill version, model, rules hash) in every sub-agent log header. Run manifests that tie together all artifacts from a pipeline execution into a single auditable record. Decision lineage: trace any output back to which model, skill version, and rules produced it.
389
+
390
+ **Security pattern updates.** A helper skill that pulls the latest vulnerability patterns and edge cases into the test-audit pipeline. Keeps your security coverage current without manual curation.
391
+
392
+ **Framework-specific Justfiles.** Auto-detect your project's framework (Next.js, Django, FastAPI, Actix, etc.) and generate tailored `just` recipes with the right build, test, and lint commands out of the box.
393
+
394
+ **Agent memory.** Persistent memory for sub-agents across invocations. Agents remember patterns from previous runs — common failure modes, project-specific conventions, recurring issues — and apply that context automatically.
395
+
396
+ **Smarter pipeline routing.** Better orchestration for review-then-fix workflows. When a code review finds issues, automatically route to fix validation without manual intervention. Tighter feedback loops between review, fix, and retest stages.
397
+
398
+ ---
399
+
360
400
  ## License
361
401
 
362
402
  [MIT](LICENSE)
package/hooks/hooks.json CHANGED
@@ -35,6 +35,17 @@
35
35
  ]
36
36
  }
37
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
+ ],
38
49
  "SessionStart": [
39
50
  {
40
51
  "hooks": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qball-inc/the-bulwark",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
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
5
  "license": "MIT",
6
6
  "author": "Ashay Kubal <https://ashaykubal.com>",
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # enforce-quality.sh - Quality gate + pipeline suggestion (chained execution)
2
+ # enforce-quality.sh - Quality gate + changed-files accumulator
3
3
  #
4
4
  # Called by PostToolUse hooks on Write|Edit operations.
5
5
  #
@@ -7,12 +7,16 @@
7
7
  # - Runs just typecheck, lint, build
8
8
  # - Exit 2 on failure (blocks tool call)
9
9
  #
10
- # Phase 2: Pipeline suggestion (all files)
11
- # - Chains to suggest-pipeline.sh
12
- # - Passes through stdin JSON for change analysis
10
+ # Phase 2: Accumulator write (code + script files only)
11
+ # - Appends file path to tmp/bulwark-changed-files.json
12
+ # - Excludes docs, config, infra paths via extension + prefix filter
13
+ # - Dedup on path
14
+ # - Stop hook (suggest-pipeline-stop.sh) reads accumulator and emits
15
+ # exactly ONE pipeline-suggestion block per turn — replacing the legacy
16
+ # per-edit decision:block anti-pattern (hook storm / silent crash).
13
17
  #
14
18
  # Exit codes:
15
- # 0 = All checks passed, pipeline suggestion complete
19
+ # 0 = All checks passed, accumulator updated
16
20
  # 2 = Quality gate failed (block)
17
21
  #
18
22
  # Usage: Called by hooks, not directly by users
@@ -22,14 +26,11 @@ set -euo pipefail
22
26
  # Configuration
23
27
  MAX_OUTPUT_LINES=100
24
28
 
25
- # Color codes (if terminal supports)
26
- RED='\033[0;31m'
27
- GREEN='\033[0;32m'
29
+ # Color codes (if terminal supports) — used for warning banners only
28
30
  YELLOW='\033[0;33m'
29
31
  NC='\033[0m' # No Color
30
32
 
31
33
  # Get directories
32
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
33
34
  PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
34
35
  LOGS_DIR="${PROJECT_DIR}/logs"
35
36
  HOOKS_LOG="${LOGS_DIR}/hooks.log"
@@ -48,10 +49,18 @@ echo "[${TIMESTAMP}] PostToolUse: enforce-quality.sh triggered for ${FILE_PATH_F
48
49
  # Extract file path from input
49
50
  FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
50
51
 
51
- # Skip infrastructure directories (no quality checks or pipeline suggestions)
52
+ # Normalize absolute path to repo-relative (if under PROJECT_DIR)
53
+ REL_PATH="$FILE_PATH"
54
+ if [ -n "$FILE_PATH" ] && [ "${PROJECT_DIR}" != "." ] && [[ "$REL_PATH" == "$PROJECT_DIR/"* ]]; then
55
+ REL_PATH="${REL_PATH#"$PROJECT_DIR/"}"
56
+ fi
57
+
58
+ # Skip infrastructure directories (no quality checks or pipeline suggestions).
59
+ # Matches against REPO-RELATIVE path — absolute-path glob matching was a bug:
60
+ # any project under a path containing /tmp/, /logs/, etc. was ignored entirely.
52
61
  # DEF-005: Prevents infinite loops when writing to logs/
53
- case "$FILE_PATH" in
54
- */logs/*|logs/*|*/tmp/*|tmp/*|*/.claude/*|.claude/*|*/node_modules/*|node_modules/*)
62
+ case "$REL_PATH" in
63
+ logs/*|tmp/*|.claude/*|node_modules/*|dist/*|build/*|.git/*)
55
64
  exit 0
56
65
  ;;
57
66
  esac
@@ -145,22 +154,62 @@ if is_code_file "$FILE_PATH"; then
145
154
  }
146
155
  fi
147
156
 
148
- # Export quality results for suggest-pipeline.sh
149
- export QUALITY_CHECKS_PASSED="true"
150
157
  fi
151
158
  fi
152
159
  fi
153
160
 
154
161
  # ============================================================
155
- # PHASE 2: Pipeline Suggestion (all file types)
162
+ # PHASE 2: Accumulator Write (code + script files only)
156
163
  # ============================================================
164
+ #
165
+ # Legacy Phase 2 chained to suggest-pipeline.sh, which emitted one
166
+ # decision:block per edit — cascading into N interrupts on multi-edit turns.
167
+ # Replaced by: append qualifying file path to accumulator; Stop hook
168
+ # (suggest-pipeline-stop.sh) emits exactly one block at turn end.
169
+
170
+ # Skip accumulator write if no file path (defensive)
171
+ if [ -z "$FILE_PATH" ]; then
172
+ exit 0
173
+ fi
174
+
175
+ # --- Exclusion filter: extensions ---
176
+ # Docs, config, data files do not warrant pipeline orchestration.
177
+ # Prefix exclusions (logs/, tmp/, .claude/, node_modules/, dist/, build/, .git/)
178
+ # already exited at the top-of-file skip. REL_PATH is also pre-computed there.
179
+ FILENAME=$(basename "$FILE_PATH")
180
+ EXTENSION="${FILENAME##*.}"
181
+ EXTENSION_LOWER=$(echo "$EXTENSION" | tr '[:upper:]' '[:lower:]')
182
+
183
+ case "$EXTENSION_LOWER" in
184
+ md|markdown|json|jsonc|yaml|yml|xml|csv|tsv|txt|docx|xlsx|pdf|html|htm)
185
+ exit 0
186
+ ;;
187
+ esac
188
+
189
+ # --- Accumulator: ensure directory, validate, append with dedup ---
190
+ ACCUMULATOR_DIR="${PROJECT_DIR}/tmp"
191
+ ACCUMULATOR="${ACCUMULATOR_DIR}/bulwark-changed-files.json"
192
+ mkdir -p "$ACCUMULATOR_DIR"
157
193
 
158
- SUGGEST_SCRIPT="${SCRIPT_DIR}/suggest-pipeline.sh"
194
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
195
+ ACC_TIMESTAMP=$(date -Iseconds)
196
+
197
+ # Recover from corrupt accumulator
198
+ if [ -f "$ACCUMULATOR" ] && ! jq -e '.' "$ACCUMULATOR" >/dev/null 2>&1; then
199
+ rm -f "$ACCUMULATOR"
200
+ fi
159
201
 
160
- if [ -x "$SUGGEST_SCRIPT" ]; then
161
- # Pass through the original input to suggest-pipeline.sh
162
- echo "$INPUT" | "$SUGGEST_SCRIPT"
202
+ if [ ! -f "$ACCUMULATOR" ]; then
203
+ jq -n --arg p "$REL_PATH" --arg t "$TOOL_NAME" --arg ts "$ACC_TIMESTAMP" \
204
+ '{version: "1.0", files: [{path: $p, tool: $t, time: $ts}]}' > "$ACCUMULATOR"
163
205
  else
164
- # suggest-pipeline.sh not found - not an error, just skip
165
- exit 0
206
+ # Dedup: append only if path not already present
207
+ EXISTING=$(jq -r --arg p "$REL_PATH" '.files[] | select(.path == $p) | .path' "$ACCUMULATOR" 2>/dev/null || echo "")
208
+ if [ -z "$EXISTING" ]; then
209
+ jq --arg p "$REL_PATH" --arg t "$TOOL_NAME" --arg ts "$ACC_TIMESTAMP" \
210
+ '.files += [{path: $p, tool: $t, time: $ts}]' "$ACCUMULATOR" > "${ACCUMULATOR}.tmp" \
211
+ && mv "${ACCUMULATOR}.tmp" "$ACCUMULATOR"
212
+ fi
166
213
  fi
214
+
215
+ exit 0
@@ -0,0 +1,118 @@
1
+ #!/bin/bash
2
+ # suggest-pipeline-stop.sh - Stop-hook handler for consolidated pipeline suggestions
3
+ #
4
+ # Fires at the end of every Claude turn. Reads the changed-files accumulator
5
+ # written by enforce-quality.sh and emits exactly one `decision: block`
6
+ # pipeline suggestion per turn, citing all qualifying files.
7
+ #
8
+ # Replaces the PostToolUse-per-edit `decision: block` anti-pattern from
9
+ # legacy suggest-pipeline.sh, which cascaded N interruptions on multi-edit
10
+ # turns (the "hook storm" / "silent crash").
11
+ #
12
+ # Input (stdin): Stop hook JSON per Anthropic spec:
13
+ # - stop_hook_active (bool) recursion guard; true if already firing
14
+ # - stop_reason (string) end_turn | user_interrupt | max_tokens
15
+ # - session_id (string) current session id
16
+ # - transcript_path (string) path to turn transcript
17
+ # - cwd (string) working directory
18
+ # - permission_mode (string) current permission mode
19
+ # - hook_event_name (string) always "Stop" here
20
+ #
21
+ # Output (stdout):
22
+ # - {"decision": "block", "reason": ...} when accumulator is non-empty;
23
+ # prevents Claude from ending the turn until the suggested pipeline runs.
24
+ # - {} otherwise (silent pass).
25
+ #
26
+ # NOTE: Stop hooks do NOT support `hookSpecificOutput` / `additionalContext`
27
+ # per the Claude Code schema (https://code.claude.com/docs/en/hooks). Valid
28
+ # top-level fields for Stop hooks: decision, reason, continue, stopReason,
29
+ # suppressOutput, systemMessage. All instruction content must live in `reason`.
30
+ #
31
+ # Accumulator contract: see scripts/hooks/enforce-quality.sh (writer).
32
+ # Path: ${CLAUDE_PROJECT_DIR:-$(pwd)}/tmp/bulwark-changed-files.json
33
+ # Schema: {"version": "1.0", "files": [{"path", "tool", "time"}]}
34
+
35
+ set -euo pipefail
36
+
37
+ # Read stdin once
38
+ INPUT=$(cat)
39
+
40
+ # Resolve project directory
41
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
42
+ LOGS_DIR="${PROJECT_DIR}/logs"
43
+ ACCUMULATOR="${PROJECT_DIR}/tmp/bulwark-changed-files.json"
44
+
45
+ mkdir -p "$LOGS_DIR"
46
+ HOOKS_LOG="${LOGS_DIR}/hooks.log"
47
+ TIMESTAMP=$(date -Iseconds)
48
+
49
+ # --- Recursion guard ---
50
+ # If a prior Stop hook already blocked and Claude is now responding to that
51
+ # block, stop_hook_active is true. Short-circuit to prevent infinite loops.
52
+ STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
53
+ if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
54
+ echo "[${TIMESTAMP}] Stop: recursion guard active, exiting silently" >> "$HOOKS_LOG"
55
+ echo '{}'
56
+ exit 0
57
+ fi
58
+
59
+ # --- Accumulator absent or empty → silent pass ---
60
+ if [ ! -f "$ACCUMULATOR" ]; then
61
+ echo '{}'
62
+ exit 0
63
+ fi
64
+
65
+ # Validate JSON; corrupt → drop and exit silently
66
+ if ! jq -e '.' "$ACCUMULATOR" >/dev/null 2>&1; then
67
+ echo "[${TIMESTAMP}] Stop: corrupt accumulator, resetting" >> "$HOOKS_LOG"
68
+ rm -f "$ACCUMULATOR"
69
+ echo '{}'
70
+ exit 0
71
+ fi
72
+
73
+ FILE_COUNT=$(jq -r '.files | length' "$ACCUMULATOR" 2>/dev/null || echo "0")
74
+ if [ "$FILE_COUNT" = "0" ]; then
75
+ echo '{}'
76
+ exit 0
77
+ fi
78
+
79
+ # --- Categorize files for targeted pipeline suggestion ---
80
+ CODE_FILES=$(jq -r '[.files[] | select(.path | test("\\.(ts|tsx|js|jsx|py|go|rs|java|cpp|c|rb|php|swift|kt)$"; "i"))] | length' "$ACCUMULATOR")
81
+ TEST_FILES=$(jq -r '[.files[] | select(.path | test("(test|spec|_test)\\.(ts|tsx|js|jsx|py|go|rs|java|cpp|rb)$"; "i"))] | length' "$ACCUMULATOR")
82
+ SCRIPT_FILES=$(jq -r '[.files[] | select(.path | test("\\.(sh|bash|zsh|fish|ps1)$"; "i"))] | length' "$ACCUMULATOR")
83
+
84
+ # Choose dominant pipeline
85
+ RECOMMENDED_PIPELINE="Code Review"
86
+ if [ "$TEST_FILES" -gt 0 ] && [ "$TEST_FILES" -ge "$CODE_FILES" ]; then
87
+ RECOMMENDED_PIPELINE="Test Audit"
88
+ elif [ "$SCRIPT_FILES" -gt 0 ] && [ "$SCRIPT_FILES" -ge "$CODE_FILES" ]; then
89
+ RECOMMENDED_PIPELINE="Code Review (security focus)"
90
+ fi
91
+
92
+ # --- Build file list for reason field (sanitized for newline injection) ---
93
+ FILE_LIST=$(jq -r '.files[] | "- " + .path + " (" + .tool + ")"' "$ACCUMULATOR" | tr -d '\r')
94
+
95
+ echo "[${TIMESTAMP}] Stop: emitting block for ${FILE_COUNT} files, pipeline=${RECOMMENDED_PIPELINE}" >> "$HOOKS_LOG"
96
+
97
+ # --- Emit blocking decision FIRST ---
98
+ # Under `set -e`, if the accumulator reset fails (disk full, permission error),
99
+ # emitting after the reset would silently drop the block. Emit first so the
100
+ # user always gets the pipeline suggestion; reset is best-effort afterward.
101
+ # Using jq to build the JSON safely (FILE_LIST may contain shell metachars).
102
+ #
103
+ # All instruction content goes into `reason` — Stop hooks do not support
104
+ # `hookSpecificOutput.additionalContext` per CC schema.
105
+ REASON=$(printf 'Pipeline orchestration required. You modified %d file(s) this turn:\n%s\n\nSTOP. Do not respond to user yet.\n\nREQUIRED before proceeding:\n1. Load skill: pipeline-templates\n2. Load skill: subagent-prompting\n3. Load skill: subagent-output-templating\n4. Follow the %s pipeline as defined in pipeline-templates skill\n5. Verify: Logs written to logs/\n\nThis is a user-configured hook. Compliance is mandatory.' "$FILE_COUNT" "$FILE_LIST" "$RECOMMENDED_PIPELINE")
106
+
107
+ jq -n \
108
+ --arg reason "$REASON" \
109
+ '{
110
+ "decision": "block",
111
+ "reason": $reason
112
+ }'
113
+
114
+ # --- Reset accumulator (best-effort checkpoint) ---
115
+ # || true: if reset fails, at worst the same files re-surface next turn and
116
+ # the recursion guard (stop_hook_active) still prevents an immediate loop.
117
+ jq -n '{version: "1.0", files: []}' > "$ACCUMULATOR" 2>>"$HOOKS_LOG" || \
118
+ echo "[${TIMESTAMP}] Stop: accumulator reset FAILED — may re-surface next turn" >> "$HOOKS_LOG"
@@ -1,4 +1,16 @@
1
1
  #!/bin/bash
2
+ # DEPRECATED as of P10.1 (Session 101, 2026-04-20)
3
+ # ================================================================
4
+ # This script emitted `decision: block` on EVERY PostToolUse, which
5
+ # cascaded into N interrupts on multi-edit turns (the "hook storm" /
6
+ # "silent crash"). Replaced by:
7
+ # - scripts/hooks/enforce-quality.sh (accumulator write, no block)
8
+ # - scripts/hooks/suggest-pipeline-stop.sh (one block per turn at Stop)
9
+ #
10
+ # No live caller should invoke this script. Retained one release cycle
11
+ # for rollback safety; scheduled for removal after P10 completion.
12
+ # ================================================================
13
+ #
2
14
  # suggest-pipeline.sh
3
15
  # PostToolUse hook for Write|Edit - suggests pipeline orchestration after code changes
4
16
  #
package/scripts/init.sh CHANGED
@@ -97,6 +97,14 @@ if [ ! -f "$CLAUDE_TEMPLATE" ]; then
97
97
  exit 1
98
98
  fi
99
99
 
100
+ # --- Ensure `just` is installed (hard dependency for scaffold-produced Justfiles) ---
101
+
102
+ # shellcheck source=./install-just.sh
103
+ source "$SCRIPT_DIR/install-just.sh"
104
+ install_just
105
+
106
+ echo ""
107
+
100
108
  # --- Install CLAUDE.md ---
101
109
 
102
110
  if [ -f "$CLAUDE_TARGET" ]; then