plugins/build/skills/build-bash-script/SKILL.md
Scaffolds a standalone Bash 4.0+ script — a single-file CLI tool, glue automation, or ops utility — with explicit shebang, `set -euo pipefail`, header comment, `readonly` constants, `die` helper, `local` variables, `main` function, and a sourceable guard. Use when the user wants to "scaffold a bash script", "create a bash script", or "scaffold a shell script". Not for POSIX `sh` portability targets, Claude Code hooks, or scripts that would be cleaner in Python — route to the appropriate primitive.
npx skillsauth add bcbeidel/wos build-bash-scriptInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Scaffold a standalone Bash 4.0+ script: a single-file program built from existing CLI tools that runs from a terminal or Makefile, returns a useful exit code, and stays composable in pipelines. The authoring rubric — anatomy template, patterns that work — lives in bash-script-best-practices.md. This skill is the workflow; the principles doc is the rubric.
This skill is bash-only by scope. POSIX sh portability targets
(dash, BusyBox, Alpine) are out of scope and refused at the Scope
Gate. It is also not for Claude Code hooks (/build:build-hook), not
for tasks better expressed in Python (/build:build-python-script),
and not for multi-file Bash applications (those want a real language).
Workflow sequence: 1. Route → 2. Scope Gate → 3. Elicit → 4. Draft → 5. Safety Check → 6. Review Gate → 7. Save → 8. Test
Also fires when the user phrases the request as:
Confirm a standalone Bash script is the right primitive and that Bash is the right language before asking scaffold-specific questions.
Wrong primitive:
/build:build-hook. Hooks have a settings.json
registration, a tool_input payload contract, and lifecycle
semantics a standalone script doesn't express./build:build-skill./build:build-rule.Wrong language — should be Python instead: see primitive-routing.md §Language Selection.
Right primitive and right language (CLI glue stitching git /
curl / jq / find / xargs; Makefile-invoked automation; one-shot
ops utility; <300 LOC of bash logic) → proceed to Scope Gate.
Refuse to scaffold — and recommend an alternative — when the request signals bash-script is the wrong tool. Probe for any of:
sh portability needed — dash, BusyBox, Alpine, or
any environment where Bash is unavailable. Bash 4.0+ is this
skill's scope; portable sh is out of scope. Recommend the user
either install Bash in the target environment or rewrite the
logic in a portable language. Do not scaffold a "portable-ish
bash" script — silent bashisms under #!/bin/sh fail on those
targets.PATH poisoning, IFS injection, signal handling). This skill
does not scaffold setuid scripts; recommend a compiled wrapper
(sudo, a tiny C/Go binary) that delegates to the unprivileged
bash script./build:build-python-script.If any signal fires, state the signal, name the recommended alternative, and stop. Do not proceed to Elicit.
If $ARGUMENTS is non-empty, parse it as [purpose] and pre-fill the
purpose field. Otherwise ask, one question at a time:
1. Purpose — one sentence: what does this script do? Preferably verb-phrased ("rotate the logs in /var/log/app and gzip the oldest 30 days").
2. Invocation style — pick one:
cli — accepts flags and positional args; has --help output.
Default for anything a human invokes directly.glue — fixed positional args, called from a Makefile or another
script. Minimal argument-parsing surface.library — sourceable for testing (. ./script.sh) but also
runnable directly via the sourceable guard. The default scaffold
already supports this; pick when the user will write bats /
shunit2 against internal functions.3. Input shape — where does the script read from?
args — positional arguments and/or flags via getopts or hand
parsing.stdin — reads from stdin, supports - as stdin sentinel.none — no input beyond flags or env vars.4. Output destination — where does primary output go?
stdout — default; data to stdout, logs to stderr. Composable in
pipelines.file — writes to a path provided as a flag.none — the script is called for its side effects (filesystem
changes, network calls).5. Destructive operations? — does the script delete, overwrite,
move files, or make irreversible network calls? If yes, the scaffold
adds a --dry-run flag (default off but visible) and a --yes
confirmation flag, plus the if [[ "${dry_run}" ]]; then ... branch
in main. If no, those are omitted.
6. External CLI dependencies — which tools does the script call
beyond Bash builtins? (e.g., jq, curl, git, rsync, find,
xargs.) These populate the command -v preflight.
7. Save path — where should the script land? No default; common
homes: scripts/, bin/, .claude/scripts/,
plugins/<name>/scripts/, .github/scripts/. Ask explicitly.
Produce two artifacts.
Artifact 1: The script.
One conditionalized template. Sections marked (if destructive) or (if has-deps) are omitted when the intake rules them out.
#!/usr/bin/env bash
#
# <progname> — <one-line purpose>
#
# Usage:
# <progname> [options] <args>
#
# Dependencies: <comma-separated list of external CLI tools>
#
# Exit codes:
# 0 success
# 1 general failure
# 64 usage error
# 69 missing required dependency
set -euo pipefail
readonly PROGNAME="$(basename "${0}")"
readonly DEFAULT_TIMEOUT=30 # named constant
REQUIRED_CMDS=(jq curl) # (if has-deps) populated from intake
usage() {
cat <<'EOF'
<progname> — <purpose>
Usage:
<progname> [options] <args>
Options:
--dry-run Print planned actions; take none. # (if destructive)
--yes Skip confirmation for destructive ops. # (if destructive)
-h, --help Show this help and exit.
EOF
}
die() {
printf 'error: %s\n' "$*" >&2
exit 1
}
preflight() { # (if has-deps)
local missing=()
local cmd
for cmd in "${REQUIRED_CMDS[@]}"; do
if ! command -v "${cmd}" >/dev/null 2>&1; then
missing+=("${cmd}")
fi
done
if [[ "${#missing[@]}" -gt 0 ]]; then
die "missing required commands: ${missing[*]}"
fi
}
main() {
case "${1:-}" in
-h|--help) usage; exit 0 ;;
esac
preflight # (if has-deps)
local input="${1:?input required}"
# validate inputs before destructive work
[[ -e "${input}" ]] || die "not found: ${input}"
# <body — split into small functions as the script grows>
}
# Sourceable guard: run main only when executed, not when sourced.
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
(if not destructive) Omit the --dry-run and --yes lines from
usage() and skip the if [[ "${dry_run}" ]]; then ... branch. The
rest stays.
(if no external deps) Omit REQUIRED_CMDS, preflight(), and the
preflight call. Drop the Dependencies: header line.
(library invocation style) No scaffold changes — the default
structure (main orchestrator, sourceable guard) already supports
. ./script.sh for testing.
Artifact 2: A suggested invocation line — how the user or a
Makefile would call the script, ready to paste. Include chmod +x so
the shebang + executable-bit contract holds.
Present both artifacts to the user before any safety checks.
Review the draft against the rubric in bash-script-best-practices.md before presenting. Group the checks:
Structure. Shebang is #!/usr/bin/env bash (or #!/bin/bash
when explicitly justified). set -euo pipefail is in the prologue. A
header comment names purpose, usage, dependencies, and exit codes.
Top-level configuration is readonly. A main function exists. The
sourceable guard [[ "${BASH_SOURCE[0]}" == "${0}" ]] invokes main.
Quoting & idioms. Every variable expansion is quoted ("$var",
"$(cmd)", "${arr[@]}"). "$@" forwards arguments. Conditionals
use [[ ... ]], not [ ... ]. Command substitution uses $(), not
backticks. printf is used for non-trivial output, not echo.
Safety. No eval. No hardcoded /tmp/ paths (use mktemp and
pair with trap). No rm -rf $var unquoted or unvalidated. No
GNU-specific flags without a declared dependency. cd invocations
check the exit status. -- precedes untrusted arguments to
option-parsing commands.
Function discipline. Function-scoped variables use local. The
die/error helper writes to stderr and exits non-zero — no bare
exit 1 with no message.
Tooling readiness. The output is structured to pass shellcheck
(quoted variables, $() over backticks, [[ ]] over [ ]) and
shfmt -i 2 -ci -bn (2-space indent, switch-case indent, binop on
next line).
Detector-script hygiene (applies to pattern-scanners). When the
draft scans source for forbidden constructs (header naming detect /
scanner / linter, or destination under check-*/scripts/), apply the
Detector-Script Pattern Hygiene rules from
bash-script-best-practices.md:
paraphrase docstrings/comments/identifiers and split regex literals so
the scanned byte sequence is non-contiguous in source.
If any check fails, revise the draft before presenting. The Review Gate is for user approval, not correctness recovery.
Present both artifacts (script + invocation line) and wait for explicit user approval before writing any file to disk. Write only after this gate passes.
If the user requests changes, revise and re-present. Continue until the user explicitly approves or cancels. Proceed to Save only on explicit approval.
Write the approved script to the path elicited in Step 3.7. Mark it executable:
chmod +x <path>
A shebang without +x is a lie — the executable bit is part of the
contract the principles doc names. Show the suggested invocation line
for the user to wire into a Makefile, CI config, or README.
Offer the audit:
"Run
/build:check-bash-script <path>to audit the scaffolded script against ShellCheck, shfmt, and the deterministic + judgment dimensions?"
The audit is the canonical follow-on; running it once after scaffold catches anything the Safety Check missed and gives the user a baseline-clean starting point.
#!/bin/sh — this skill is bash-only. Bash
features under a sh shebang fail silently on dash/BusyBox.
Refuse via Scope Gate signal #1; do not produce a "mostly portable"
hybrid.--dry-run — if Intake step 3.5 flagged
destructive operations, the draft must wire the dry-run flag into
the destructive code path, not just declare the flag and ignore
it. Show the if [[ "${dry_run}" ]]; then ... branch in main.REQUIRED_CMDS — when external deps are intaken, the
array is populated. Empty array silently skips preflight, killing
the fail-fast contract.--dry-run flag is only scaffolded when Intake step 3.5
flagged destructive operations. Do not add it unconditionally — it
clutters read-only scripts.command -v preflight is only scaffolded when Intake step 3.6
named external dependencies. Do not add an empty preflight as
"best-effort" structure — it is dead code.Chainable to: /build:check-bash-script (audit the scaffolded
script against ShellCheck, shfmt, and the judgment dimensions).
tools
Use when the user wants to "audit a help skill", "review my plugin index", or "verify my help-skill is up to date". Audits a plugins/<plugin>/skills/help/SKILL.md against the help-skill rubric — coverage, freshness, frontmatter fidelity, plus five judgment dimensions and a trigger-collision check.
tools
Use when the user wants to "scaffold a help skill", "add a /<plugin>:help command", or "build a plugin index skill", or wants to give a plugin an orientation surface that lists its skills and common workflows. Produces a SKILL.md at plugins/<plugin>/skills/help/SKILL.md.
tools
Audits pair-level integrity of a primitive-pair (the artifact `/build:build-skill-pair` produces) by walking the four required artifact slots — principles doc, `build-<primitive>/SKILL.md`, `check-<primitive>/SKILL.md`, and the `primitive-routing.md` registration — and reports cross-artifact issues a per-SKILL.md checker cannot see: missing principles doc, divergent principles paths between halves, absent routing registration, missing build→check handoff. Per-half structural compliance with the unified pattern (`check-skill-pattern.md`) is delegated to `plugins/build/_shared/scripts/check_skill_pattern.py`. Use when the user wants to "audit a skill pair", "review a primitive pair", or "validate the skill pair for X". Not for auditing a single SKILL.md — route to `/build:check-skill`. Not for re-distilling a stale principles doc — route to `/build:build-skill-pair`.
testing
Audit a root-level resolver — verify AGENTS.md pointer, managed-region integrity, filing-table coverage against disk, context-table actionability, and trigger-eval pass rate. Use when the user wants to "audit a resolver", "validate routing table", or "find dark capabilities".