Initialize Bulwark infrastructure in a project: language-aware Justfile (8 langs), bun + eval-framework toolchain, logs/ subdirectories, and optional hooks.
Instalación
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Initialize Bulwark infrastructure in a project: language-aware Justfile (8 langs), bun + eval-framework toolchain, logs/ subdirectories, and optional hooks.
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 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 8a — Permission hook (opt-in): ONLY if --with-permission-hook — confirm trust (AskUserQuestion), merge PreToolUse entry into .claude/settings.json, copy bulwark-permission-hook.sh, record choice. Default (no flag) = NOT installed
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
--no-hooks - Skip hook configuration (hooks are generated by default)
--with-permission-hook - Also install the opt-in PreToolUse permission-bypass hook (auto-approves Bulwark's own bundled-asset reads/edits/scripts). Default: NOT installed
--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
If LANG_OVERRIDE is set, use that. Otherwise, search from current directory for manifest files in this order (first match wins):
package.json → node
pyproject.toml OR requirements.txt OR setup.py → python
Cargo.toml → rust
go.mod → go
build.gradle OR build.gradle.kts OR any *.gradle.kts file → kotlin
Package.swift → swift
No manifest above AND at least one *.sh file at project root or in scripts/ → shell
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
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}
{IF WITH_PERMISSION_HOOK}
- scripts/hooks/bulwark-permission-hook.sh
- .claude/settings.json (PreToolUse permission-bypass entry — merged independently of --no-hooks)
{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).
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)
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):
Copy scripts/hooks/inject-protocol.sh to ${CLAUDE_PROJECT_DIR}/scripts/hooks/
Copy scripts/hooks/enforce-quality.sh to ${CLAUDE_PROJECT_DIR}/scripts/hooks/
Copy scripts/hooks/suggest-pipeline-stop.sh to ${CLAUDE_PROJECT_DIR}/scripts/hooks/
Copy skills/governance-protocol/SKILL.md to ${CLAUDE_PROJECT_DIR}/.claude/skills/governance-protocol/SKILL.md
Create parent directories as needed (mkdir -p).
Step 8a: Optional Permission-Bypass Hook (opt-in)
ONLY if WITH_PERMISSION_HOOK is true (the --with-permission-hook flag). Default (no flag) → SKIP this entire step; the permission hook is NOT installed and nothing below runs. Set PERMISSION_HOOK=not_requested and continue to Step 9.
This installs bulwark-permission-hook.sh as a project-scopePreToolUse hook that auto-approves Read/Edit/Bash operations on Bulwark's own bundled assets — skipping the per-file permission prompts CC raises on plugin-bundled files the user already trusted at install. It is a scoped workaround for upstream CC permission bugs (retire when #29285 lands; see docs/reference/hooks.md).
Runs independently of SKIP_HOOKS — a user may pass --no-hooks --with-permission-hook (no governance hooks, but yes permission-bypass). If Step 8 was skipped and .claude/settings.json does not exist, create it here.
1. Confirm the trust decision (REQUIRED — security-sensitive). Installing a permission-bypass hook is a trust decision, so confirm explicitly even though the flag was passed. Use AskUserQuestion:
Install the Bulwark permission-bypass hook?
It auto-approves Read/Edit/Bash on Bulwark's own bundled assets (under the plugin cache root / ${CLAUDE_PLUGIN_ROOT}), so you stop seeing permission prompts for them. Everything else still prompts as normal. Writes are never auto-approved. Path-traversal that escapes the plugin root is denied. Requires trusting Bulwark at install level.
Options: Install / Skip
If the user picks Skip (or declines): do NOT install; set PERMISSION_HOOK=declined; continue to Step 9.
2. On Install:
a. Idempotency check. If .claude/settings.json already registers bulwark-permission-hook.sh under PreToolUse, skip re-adding (set PERMISSION_HOOK=already_present) and proceed to step (c) to ensure the script copy exists.
b. Merge the PreToolUse entry into .claude/settings.json (create the file and/or the hooks key if absent; preserve all existing settings — same merge discipline as Step 8):
c. Copy the hook script: copy scripts/hooks/bulwark-permission-hook.sh to ${CLAUDE_PROJECT_DIR}/scripts/hooks/ and ensure it is executable (chmod +x). Create parent dirs with mkdir -p.
d. Set PERMISSION_HOOK=installed.
Why there is no env-var gate in the project entry: the hook self-gates on $CLAUDE_PLUGIN_OPTION_ENABLE_PERMISSION_BYPASS, but project-scope installs do not set that var — the script treats an unset gate as active (bulwark-permission-hook.sh opt-in gate), so the entry's presence in settings.json IS the opt-in. (The env-var gate only matters for the plugin-level install, which defaults it to false.)
3. Record the choice in the scaffold log (Step 9): add scripts/hooks/bulwark-permission-hook.sh to reviewed_files when copied, and set actions.permission_hook.action to the PERMISSION_HOOK value.
Step 9: Write Scaffold Log
Write to logs/scaffold-{YYYYMMDD-HHMMSS}.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.shmetadata:timestamp: {ISO-8601}
action:scaffoldinvocation:"/bulwark-scaffold {args}"detection:# language must be one of: node | python | rust | go | kotlin | swift | shell | generic | fallthroughlanguage: {DETECTED_LANG}
manifest: {manifestpathornull}
override: {LANG_OVERRIDEornull}
user_selected: {true|false} # true if user picked a language at the fallthrough promptactions:justfile:action:created|skipped|overwrittenbackup: {backuppathornull}
# 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.justtemplate:lib/templates/justfile-{RESOLVED_TEMPLATE}.justlogs_directory:created:truesubdirectories: [diagnostics, validations, debug-reports]
gitignore:action:created|updated|skippedpatterns_added:7hooks:action:created|merged|skippedpath:.claude/settings.jsonskipped_reason: {reasonifskipped}
permission_hook:# --with-permission-hook opt-in (Step 8a). not_requested when the flag is absent.action:installed|declined|already_present|not_requestedpath:.claude/settings.jsonscript:scripts/hooks/bulwark-permission-hook.shsummary:|
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)}
**Permission hook:** {installed|declined|already present|not installed}
**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."