Initialize Bulwark infrastructure in a project: language-aware Justfile (8 langs), bun + eval-framework toolchain, logs/ subdirectories, and optional hooks.
Install to Claude Code
npx -y skills add QBall-Inc/the-bulwark --skill bulwark-scaffold --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Bulwark Scaffold?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/qball-inc-bulwark-scaffold)More formats (shields.io, HTML) on the badges page.
---
name: bulwark-scaffold
description: Initialize Bulwark infrastructure in a project: language-aware Justfile (8 langs), bun + eval-framework toolchain, logs/ subdirectories, and optional hooks.
when_to_use: Use when the user asks to set up Bulwark in a project, scaffold a Justfile, configure Bulwark hooks, or initialize the eval framework. Do NOT auto-invoke during normal development — this writes files, runs installers, and modifies .gitignore. Also invoked by the Bulwark init skill when scaffolding is selected.
argument-hint: "[--force] [--no-hooks] [--dry-run] [--lang=<node|python|rust|go|kotlin|swift|shell|generic>]"
user-invocable: true
version: 1.0.1
author: "Ashay Kubal @ Qball Inc."
---
# Bulwark Scaffold
Initialize Bulwark infrastructure in a project by generating Justfile templates, creating the logs directory structure, and optionally configuring hooks.
---
## Mandatory Execution Checklist (BINDING)
**Every item below is mandatory. No deviations. No substitutions. No skipping. Skipping items violates SC1-SC3 (Skill Compliance Rules in Rules.md).**
You are the orchestrator. Follow every item in order. Do NOT return to the user until all applicable items are checked.
- [ ] **Step 1 — Parse arguments**: `--force`, `--no-hooks`, `--dry-run`, `--lang=<...>` extracted from `$ARGUMENTS`
- [ ] **Step 2 — Detect project language**: If `--lang` not supplied, project files inspected (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, etc.); defaults to `generic` when no signal found
- [ ] **Step 3 — `just` runtime check**: `command -v just` runs; if missing, follow Step 3 install path
- [ ] **Step 3.5a — `bun` runtime check**: `bash <resolved-installer-path> --verify` invoked (NOT `command -v bun` — that bypasses version check); follow Step 3.5 install path if exit non-zero
- [ ] **Step 3.5b — Installer copy**: `scripts/install-bun.sh` copied to `${CLAUDE_PROJECT_DIR}/scripts/` so Justfile recipes can reference it project-relatively
- [ ] **Step 3.6 — Existing Justfile check**: If a Justfile is already present, abort unless `--force` (with backup created)
- [ ] **Step 4 — Dry-run check**: If `--dry-run`, preview the planned writes and exit without modifying disk
- [ ] **Step 5 — Justfile generation**: Template emitted from `lib/templates/justfile-<lang>.just`; bun + eval recipes (`install-bun`, `verify-bun`, `eval-skill`, `eval-grade`, `eval`) confirmed present in output
- [ ] **Step 6 — `logs/` subdirectories**: `diagnostics/`, `validations/`, `debug-reports/` created
- [ ] **Step 7 — `.gitignore`**: Bulwark log patterns appended idempotently
- [ ] **Step 8 — Hooks**: If plugin-level hooks active, SKIP `.claude/settings.json` hook injection (anti-duplication)
- [ ] **Step 9 — Scaffold log**: `logs/scaffold-{ts}.yaml` written with top-level `reviewed_files: [...]` (Stop-hook contract)
- [ ] **Step 10 — Report results**: User-facing summary emitted listing files written, installer outcomes, and any skipped steps
---
## Usage
```
/bulwark-scaffold [options]
```
**Options:**
- `--force` - Overwrite existing Justfile (creates backup)
- `--no-hooks` - Skip hook configuration (hooks are generated by default)
- `--dry-run` - Show what would be generated without writing files
- `--lang=<node|python|rust|go|kotlin|swift|shell|generic>` - Override language detection
**Examples:**
- `/bulwark-scaffold` - Full scaffold with Justfile + logs/ + hooks
- `/bulwark-scaffold --force` - Overwrite existing Justfile
- `/bulwark-scaffold --no-hooks` - Skip hook configuration
- `/bulwark-scaffold --dry-run` - Preview changes
---
## Execution Steps
### Step 1: Parse Arguments
Extract options from `$ARGUMENTS`:
- `--force` → FORCE_OVERWRITE=true
- `--no-hooks` → SKIP_HOOKS=true
- `--dry-run` → DRY_RUN=true
- `--lang=X` → LANG_OVERRIDE=X
### Step 2: Detect Project Language
If LANG_OVERRIDE is set, use that. Otherwise, search from current directory for manifest files in this order (first match wins):
1. `package.json` → **node**
2. `pyproject.toml` OR `requirements.txt` OR `setup.py` → **python**
3. `Cargo.toml` → **rust**
4. `go.mod` → **go**
5. `build.gradle` OR `build.gradle.kts` OR any `*.gradle.kts` file → **kotlin**
6. `Package.swift` → **swift**
7. No manifest above AND at least one `*.sh` file at project root or in `scripts/` → **shell**
8. None of the above → **fallthrough** (do NOT silently default to generic; see Step 5)
Store result in DETECTED_LANG variable. If fallthrough, DETECTED_LANG=`fallthrough` (a marker value, not a template name).
### Step 3: Verify `just` runtime
**Pre-flight: Verify `just` is installed.**
```
Run: command -v just
IF just is NOT found:
Delegate the install attempt to scripts/install-just.sh — it detects the
platform (macOS/WSL/Debian/RHEL/Windows) and uses the right package
manager (brew/cargo/apt/dnf/winget/scoop) with cargo as primary for
Linux. Does NOT use curl|bash.
Locate the installer:
- Plugin install: ${CLAUDE_PLUGIN_ROOT}/scripts/install-just.sh
- Local dev: ${CLAUDE_PROJECT_DIR}/scripts/install-just.sh
Ask user: "just is required for the scaffolded Justfile. Install it now via scripts/install-just.sh (recommended) or skip Justfile generation?"
- If install: Run `bash <installer-path>`
- Exit 0: just installed → continue
- Exit non-zero: installer already printed actionable per-platform
instructions → set SKIP_JUSTFILE=true (do not re-print)
- If skip: Set SKIP_JUSTFILE=true
If the installer script cannot be located (rare — repo corruption):
Print: "Could not find scripts/install-just.sh. Install just manually:"
Print: " macOS: brew install just"
Print: " Linux: cargo install just (requires Rust: https://rustup.rs)"
Print: " Windows: winget install Casey.Just"
Print: " All: https://just.systems/man/en/chapter_4.html"
Set SKIP_JUSTFILE=true
```
### Step 3.5: Verify bun runtime (eval framework + generated TS scripts)
**Pre-flight: Verify `bun` is installed (required for create-skill eval framework + generated archetype scripts).**
```
1. Locate the source installer (prefer plugin install path; fall back to local-dev):
- Plugin install: ${CLAUDE_PLUGIN_ROOT}/scripts/install-bun.sh
- Local dev: ${CLAUDE_PROJECT_DIR}/scripts/install-bun.sh
IF neither path exists (rare — repo corruption):
Print: "Could not find scripts/install-bun.sh. Install bun manually:"
Print: " macOS: brew install oven-sh/bun/bun"
Print: " Linux: curl -fsSL https://bun.sh/install | bash"
Print: " Windows: powershell -c \"irm bun.sh/install.ps1 | iex\""
Print: " Docs: https://bun.sh/docs/installation"
Set SKIP_BUN_FEATURES=true
Skip the rest of Step 3.5
2. Copy the installer into the project so the Justfile recipes can reference
it project-relatively (the templates use `./scripts/install-bun.sh`):
mkdir -p ${CLAUDE_PROJECT_DIR}/scripts
cp <resolved-installer-path> ${CLAUDE_PROJECT_DIR}/scripts/install-bun.sh
chmod +x ${CLAUDE_PROJECT_DIR}/scripts/install-bun.sh
(Idempotent — overwriting any prior copy ensures the project mirrors the
currently-installed plugin version.)
3. Run the verify probe with the project-local path (NOT `command -v bun`,
which would bypass version checks performed by the installer):
bash ${CLAUDE_PROJECT_DIR}/scripts/install-bun.sh --verify
4. IF bun is NOT found OR version < 1.0 (verify exit non-zero):
Ask user: "bun is required for the create-skill eval framework and generated TS scripts. Install it now via scripts/install-bun.sh (recommended) or skip eval-framework features?"
- If install: Run `bash ${CLAUDE_PROJECT_DIR}/scripts/install-bun.sh`
- Exit 0: bun installed → continue
- Exit non-zero: installer already printed actionable per-platform
instructions → set SKIP_BUN_FEATURES=true (do not re-print)
- If skip: Set SKIP_BUN_FEATURES=true; user can install later via `just install-bun`
5. IF bun is found (verify exit 0):
Continue (no install needed; idempotent verify).
```
When `bun` is verified or installed AND the templates have been emitted, the recipes `just install-bun`, `just verify-bun`, `just eval-skill`, `just eval-grade`, and `just eval` are runnable. The first two delegate to the project-local `scripts/install-bun.sh` (copied in step 2); the eval recipes run TS scripts under `skills/create-skill/scripts/` via bun. When `SKIP_BUN_FEATURES=true`, the scaffold continues but the user is informed those features will fail until `just install-bun` runs successfully.
The `bun` check is parallel to the `just` check (Step 3) — both are runtime installers; both follow the same locate-then-execute pattern; both have actionable manual fallbacks.
### Step 3.6: Check for existing Justfile
```
IF Justfile exists AND NOT FORCE_OVERWRITE:
Print: "Justfile already exists. Use --force to overwrite (creates backup)."
Set SKIP_JUSTFILE=true
ELSE IF Justfile exists AND FORCE_OVERWRITE:
Create backup: Justfile.backup-{YYYYMMDD-HHMMSS}
Set SKIP_JUSTFILE=false
ELSE:
Set SKIP_JUSTFILE=false
```
### Step 4: Dry Run Check
If DRY_RUN is true, display preview and exit:
```
## Scaffold Preview (dry-run)
**Language detected:** {DETECTED_LANG} ({manifest file or "no manifest"})
**Would create:**
- Justfile (from `lib/templates/justfile-{RESOLVED_TEMPLATE}.just` — substitute the actual filename, e.g., `justfile-go.just`, NOT the interpolation placeholder)
- logs/
- logs/diagnostics/
- logs/validations/
- logs/debug-reports/
{IF NOT SKIP_HOOKS}
- .claude/settings.json (hooks configuration)
- .claude/skills/governance-protocol/SKILL.md
- scripts/hooks/inject-protocol.sh
- scripts/hooks/enforce-quality.sh
- scripts/hooks/suggest-pipeline-stop.sh
{ENDIF}
**Would update:**
- .gitignore (add Bulwark patterns)
Run without --dry-run to apply changes.
```
Then STOP execution.
### Step 5: Generate Justfile
If NOT SKIP_JUSTFILE:
**Template selection:**
If DETECTED_LANG is one of `node|python|rust|go|kotlin|swift|shell`:
- TEMPLATE_NAME = `justfile-{DETECTED_LANG}.just`
If DETECTED_LANG is `fallthrough` (no manifest detected, no --lang override):
- Do NOT silently copy `justfile-generic.just`. The generic template is now
fail-loudly (every recipe exits 1 with an "unconfigured" error), so handing
it to the user without explicit consent produces a broken-looking Justfile.
- Prompt the user:
```
No language manifest detected and no --lang override provided.
Choose a language template:
1) node - eslint + tsc + vitest recipes
2) python - ruff + mypy + pytest recipes
3) rust - cargo fmt + clippy + test recipes
4) go - go vet + golangci-lint + gofmt recipes
5) kotlin - ktlint + detekt recipes
6) swift - swiftlint + swift-format recipes
7) shell - shellcheck + shfmt recipes
8) generic - placeholder Justfile that FAILS LOUDLY on every recipe
(use only if you will hand-edit it immediately)
9) cancel - skip Justfile generation
Select [1-9]:
```
- Record the user's choice in DETECTED_LANG. If option 9, set SKIP_JUSTFILE=true.
- If option 8 (generic), print a warning before copying:
```
WARNING: The generic Justfile fails every recipe with exit 1 by design.
You must either (a) pick a language and re-run scaffold, or
(b) replace the `_not_configured` recipe calls with real tool invocations
before running `just <recipe>`.
```
- Then TEMPLATE_NAME = `justfile-{DETECTED_LANG}.just` (with the user's choice).
**Copy template:**
1. Locate template: `${CLAUDE_PLUGIN_ROOT}/lib/templates/{TEMPLATE_NAME}`
- For local development: `${CLAUDE_PROJECT_DIR}/lib/templates/{TEMPLATE_NAME}`
2. Copy template content to `Justfile` in project root
### Step 6: Create logs/ Directory Structure
```bash
mkdir -p logs/diagnostics logs/validations logs/debug-reports
touch logs/.gitkeep logs/diagnostics/.gitkeep logs/validations/.gitkeep logs/debug-reports/.gitkeep
```
### Step 7: Update .gitignore
Check if `.gitignore` exists and contains `# Bulwark logs` marker:
- If marker exists: Skip (already configured)
- If marker does not exist: Append the following patterns
**Patterns to add:**
```gitignore
# Bulwark logs
logs/*.yaml
logs/*.log
logs/diagnostics/*.yaml
logs/validations/*.yaml
logs/debug-reports/*.yaml
!logs/.gitkeep
!logs/*/.gitkeep
```
### Step 8: Generate Hook Configuration (Default)
UNLESS SKIP_HOOKS is true:
**Pre-flight: Check for existing Bulwark hooks to prevent duplication.**
Plugin hooks (`hooks/hooks.json`) and settings hooks (`.claude/settings.json`) both fire at runtime. If hooks already exist in any location, adding them again causes double execution.
**Check these locations for existing Bulwark hooks** (search for `enforce-quality` or `inject-protocol`):
| Location | Scope | Notes |
|----------|-------|-------|
| `./hooks/hooks.json` | Project plugin | Bulwark installed as project-level plugin |
| `~/.claude/plugins/*/hooks/hooks.json` | User plugin | Bulwark installed at user level via `/install` |
| `.claude/settings.json` | Project settings | Previously scaffolded |
| `.claude/settings.local.json` | Project local | User-specific project overrides |
| `~/.claude/settings.json` | User settings | Global user hooks |
| `~/.claude/settings.local.json` | User local | Global user overrides |
```
HOOKS_FOUND_IN = []
FOR each location above:
IF file exists AND contains "enforce-quality" OR "inject-protocol":
Append location to HOOKS_FOUND_IN
IF HOOKS_FOUND_IN is not empty:
Print: "Bulwark hooks already present in: {HOOKS_FOUND_IN}. Skipping hook generation to prevent duplication."
Set SKIP_HOOKS=true
```
**If no existing hooks found**, proceed with hook generation:
Check if `.claude/settings.json` exists:
- If exists: Merge hooks into existing configuration (preserve other settings)
- If not exists: Create new file
**Hook configuration to add:**
```json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/scripts/hooks/inject-protocol.sh",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/scripts/hooks/enforce-quality.sh",
"timeout": 60
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/scripts/hooks/suggest-pipeline-stop.sh",
"timeout": 30
}
]
}
]
}
}
```
**Note:** SessionStart has no matcher, so it fires for all session events (start, resume, clear, compact). The `Stop` hook fires once at Claude turn end and emits a single consolidated pipeline-suggestion block per turn (mirrors the plugin's own `hooks/hooks.json`). Do NOT install the deprecated `suggest-pipeline.sh` — it emits per-edit blocks and causes the hook storm P10.1 fixes.
**Also copy required files** (only if hooks were generated above):
1. Copy `scripts/hooks/inject-protocol.sh` to `${CLAUDE_PROJECT_DIR}/scripts/hooks/`
2. Copy `scripts/hooks/enforce-quality.sh` to `${CLAUDE_PROJECT_DIR}/scripts/hooks/`
3. Copy `scripts/hooks/suggest-pipeline-stop.sh` to `${CLAUDE_PROJECT_DIR}/scripts/hooks/`
4. Copy `skills/governance-protocol/SKILL.md` to `${CLAUDE_PROJECT_DIR}/.claude/skills/governance-protocol/SKILL.md`
Create parent directories as needed (`mkdir -p`).
### Step 9: Write Scaffold Log
Write to `logs/scaffold-{YYYYMMDD-HHMMSS}.yaml`:
```yaml
# Top-level — required for Stop-hook per-file pipeline-recursion suppression.
# List every script/.sh file generated or copied during scaffolding (e.g. the
# project copy of install-bun.sh, any hook scripts copied to scripts/hooks/).
# Paths relative to ${CLAUDE_PROJECT_DIR}. Empty list `[]` if no script files
# were touched. Missing field disables suppression for this log (strict mode).
reviewed_files:
- scripts/install-bun.sh
- scripts/hooks/enforce-quality.sh
metadata:
timestamp: {ISO-8601}
action: scaffold
invocation: "/bulwark-scaffold {args}"
detection:
# language must be one of: node | python | rust | go | kotlin | swift | shell | generic | fallthrough
language: {DETECTED_LANG}
manifest: {manifest path or null}
override: {LANG_OVERRIDE or null}
user_selected: {true|false} # true if user picked a language at the fallthrough prompt
actions:
justfile:
action: created|skipped|overwritten
backup: {backup path or null}
# template filename resolves to one of:
# justfile-node.just | justfile-python.just | justfile-rust.just
# justfile-go.just | justfile-kotlin.just | justfile-swift.just
# justfile-shell.just | justfile-generic.just
template: lib/templates/justfile-{RESOLVED_TEMPLATE}.just
logs_directory:
created: true
subdirectories: [diagnostics, validations, debug-reports]
gitignore:
action: created|updated|skipped
patterns_added: 7
hooks:
action: created|merged|skipped
path: .claude/settings.json
skipped_reason: {reason if skipped}
summary: |
Scaffold complete for {DETECTED_LANG} project.
```
### Step 10: Report Results
Present summary to user:
```
## Scaffold Complete
**Language:** {DETECTED_LANG}
**Justfile:** {created|skipped|overwritten (backup: path)}
**logs/:** Created with subdirectories (diagnostics, validations, debug-reports)
**.gitignore:** {updated|created|unchanged}
**Hooks:** {created|merged|skipped (--no-hooks)}
**Governance:** {installed|skipped} - Protocol injected at session start
Run `just` to see available recipes:
- `just typecheck` - Run type checker
- `just lint` - Run linter
- `just build` - Build project
- `just test` - Run tests
- `just ci` - Run all quality checks
- `just fix` - Auto-fix issues
```
---
## Error Handling
| Scenario | Action |
|----------|--------|
| Cannot detect language | Prompt user to pick from the Step 5 menu. Do NOT silently default to generic — it produces a fail-loudly Justfile by design. |
| Invalid --lang value | Print: "Invalid --lang. Use one of: node, python, rust, go, kotlin, swift, shell, generic" and exit. |
| Template file missing | Print: "Template not found at {path}. Bulwark installation may be corrupted." |
| Cannot create logs/ | Print: "Cannot create logs/ directory. Check permissions." |
| .gitignore write fails | Print warning, continue with other operations |
| Justfile backup fails | Print error, abort Justfile generation |
---
## Diagnostic Output
Write diagnostic log to `logs/diagnostics/bulwark-scaffold-{timestamp}.yaml`:
```yaml
# Top-level — mirror the same list emitted in the scaffold report (Stop hook contract).
reviewed_files:
- scripts/install-bun.sh
- scripts/hooks/enforce-quality.sh
skill: bulwark-scaffold
timestamp: {ISO-8601}
invocation: "{full command}"
inputs:
force: {true|false}
no_hooks: {true|false}
dry_run: {true|false}
lang_override: {value or null}
detection:
language: {detected}
manifest_path: {path or null}
outputs:
justfile_created: {true|false}
logs_created: {true|false}
gitignore_updated: {true|false}
hooks_configured: {true|false}
errors: []
```
Scanned 5/27/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!