agentsclimarketplace

Bulwark scaffold

Skill QBall-Inc/the-bulwark/skills/bulwark-scaffold

Development workflow enforcement plugin for Claude Code

Install
npx -y skills add QBall-Inc/the-bulwark --skill bulwark-scaffold

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Initialize Bulwark infrastructure in a project: language-aware Justfile (8 langs), bun + eval-framework toolchain, logs/ subdirectories, and optional hooks.

SKILL.md

23.7 KB, as published. Nobody here has run it

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, --with-permission-hook, --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 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

Usage

/bulwark-scaffold [options]

Options:

  • --force - Overwrite existing Justfile (creates backup)
  • --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
  • /bulwark-scaffold --force - Overwrite existing Justfile
  • /bulwark-scaffold --no-hooks - Skip hook configuration
  • /bulwark-scaffold --with-permission-hook - Scaffold + install the opt-in permission-bypass hook
  • /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
  • --with-permission-hook → WITH_PERMISSION_HOOK=true (default: false)
  • --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.jsonnode
  2. pyproject.toml OR requirements.txt OR setup.pypython
  3. Cargo.tomlrust
  4. go.modgo
  5. build.gradle OR build.gradle.kts OR any *.gradle.kts file → kotlin
  6. Package.swiftswift
  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}
{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).

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

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:


# 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):

LocationScopeNotes
./hooks/hooks.jsonProject pluginBulwark installed as project-level plugin
~/.claude/plugins/*/hooks/hooks.jsonUser pluginBulwark installed at user level via /install
.claude/settings.jsonProject settingsPreviously scaffolded
.claude/settings.local.jsonProject localUser-specific project overrides
~/.claude/settings.jsonUser settingsGlobal user hooks
~/.claude/settings.local.jsonUser localGlobal 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:

{
  "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 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-scope PreToolUse 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):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/scripts/hooks/bulwark-permission-hook.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

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.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}
  permission_hook:
    # --with-permission-hook opt-in (Step 8a). not_requested when the flag is absent.
    action: installed|declined|already_present|not_requested
    path: .claude/settings.json
    script: scripts/hooks/bulwark-permission-hook.sh

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)}
**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

ScenarioAction
Cannot detect languagePrompt user to pick from the Step 5 menu. Do NOT silently default to generic — it produces a fail-loudly Justfile by design.
Invalid --lang valuePrint: "Invalid --lang. Use one of: node, python, rust, go, kotlin, swift, shell, generic" and exit.
Template file missingPrint: "Template not found at {path}. Bulwark installation may be corrupted."
Cannot create logs/Print: "Cannot create logs/ directory. Check permissions."
.gitignore write failsPrint warning, continue with other operations
Justfile backup failsPrint error, abort Justfile generation

Diagnostic Output

Write diagnostic log to logs/diagnostics/bulwark-scaffold-{timestamp}.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}
  with_permission_hook: {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}
  permission_hook_configured: {true|false}
errors: []

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.