| argument-hint | [optional *.sh file or folder paths] |
| description | Format Bash (*.sh) files to match .claude/CODE_STYLE.md, the repository's source of truth for shell style. Use when the user asks to format, fix, or apply the code style to shell scripts. Runs in two modes — with no arguments it formats every *.sh file modified locally on the current branch (both committed on branch and not yet committed changes); with file or folder paths as arguments it formats those targets instead. |
| name | format-bash-source |
Format Bash Source
Reformat Bash scripts so they comply with .claude/CODE_STYLE.md, the authoritative style guide for every *.sh file in this repository.
Load the Style Guide
Read .claude/CODE_STYLE.md in full before touching any file. It is the single source of truth; every rule you apply must come from it. Do not invent rules or rely on generic Bash conventions that the document does not state. If the document and a file disagree, the document wins.
Resolve the Target Files
There are two modes. Pick the mode from whether arguments were provided.
Default Mode (No Arguments)
Format every *.sh file modified locally on the current branch — both the changes already committed on the branch (relative to master) and the changes not yet committed. Collect them with:
{
git diff --name-only --diff-filter=ACMR master...HEAD
git diff --name-only --diff-filter=ACMR
git diff --name-only --cached --diff-filter=ACMR
git ls-files --others --exclude-standard
} | sort --unique | grep '\.sh$'
This covers commits made on the branch (versus master), unstaged changes, staged changes, and new untracked scripts. If the branch's base is not master, substitute the correct base branch. If the list is empty, report that there are no locally modified *.sh files and stop.
Argument Mode (Paths Provided)
When the skill is invoked with arguments, treat each argument as a file or a folder:
-
A path ending in .sh is formatted directly.
-
A folder is expanded to every *.sh file under it:
find "${path}" -type f -name '*.sh'
-
Ignore arguments that resolve to no *.sh files, but report each one that was skipped so the user knows.
The arguments passed to this skill are: ${ARGUMENTS}
Format Each File
For every target file, read it and apply the rules from .claude/CODE_STYLE.md using Edit. Make only style changes — never alter the script's behavior, logic, or output. The checks to enforce include (this is a reminder, not a replacement for reading the document):
- File layout: shebang, blank line, sorted
source block, blank line, function definitions, single trailing main invocation, no blank last line; _-prefix for internal files.
- Functions:
function name form (no ()), snake_case, verb prefixes, _-prefix for local functions, globals declared before locals.
- Variables:
UPPER_SNAKE_CASE for globals/env, lower_snake_case for locals (_UPPER when shared across local functions), local declarations, no spaces around =, always braced and quoted ("${var}"), the whole parameter quoted when a variable is adjacent to literal text ("release.${name}.pom", not release."${name}".pom; literal kept outside quotes only for a glob or for a Git URL/ref/refspec/tag passed to git), locals declared close to first use rather than batched at the top, the enumerated Bash-specific parameter expansions (${var##*/}, ${var%/*}, ${var##*:}, ${var%.*}, ${var#prefix}, ${var/a/b}) rewritten to the legible equivalents listed in CODE_STYLE.md (every other expansion left unchanged), $((...)) arithmetic with no inner spaces, $(...) over backticks, a single command substitution, parameter expansion, or arithmetic expansion left unquoted when it is the entire right-hand side of a bare x=, local x=, export x=, readonly x=, or declare x= assignment, no trailing ; at the end of a $( ... ).
- Sorting:
source lines, function definitions, and same-location local variable declarations sorted case-sensitively (../ before ./). This is mechanical, not a judgment call — apply it even when the current order looks like a deliberate "template" and even when it means moving whole function bodies. Functions sort alphabetically with globals before locals and main in its alphabetical position (it is not pinned first or last); reordering definitions never changes behavior, since execution order comes from the calls in , not the definition order.
Skip .claude/CODE_STYLE.md itself and any non-*.sh file.
Verify Idempotency
The formatter must be idempotent: a second run on an unchanged tree must produce zero edits. To guarantee this:
- Only edit code that strictly violates a rule you can cite by name. Never replace one compliant form with another equally-compliant form.
- After editing a file, rescan it. If a second pass would make any further change, either make it now or recognize that the triggering rule is too subjective to auto-apply — and leave the code alone.
- The "too subjective" exception is narrow: it covers only rules that genuinely require judgment, never deterministic ones. Sorting (
source lines, function definitions, local declarations), bracket choice, quoting, and the enumerated parameter-expansion rewrites are all mechanical and must always be applied — do not skip them because the change is large (e.g. moving a function body) or because the existing order resembles an intentional layout.
Verification Sweep
Reading each file is the primary check, but it is easy to miss a class of violation across many files — especially when the work is split across several passes or subagents that may apply a rule unevenly. After editing, rescan the target files with these greps and confirm every remaining hit is intentional. Each is a deterministic rule, so a hit is either a real violation to fix now or an explainable exception (and never both):
grep -nE '\b(if|elif|while) +!? *\(' "${files[@]}"
grep -nE '^[[:space:]]*(local |export |readonly |declare (-[A-Za-z]+ )?)?[A-Za-z_][A-Za-z0-9_]*="(\$\{[A-Za-z0-9_@#?]+\}|\$\(\([^"]*\)\)|\$\([^")]*\))"[[:space:]]*$' "${files[@]}"
grep -nE '"\$\{[A-Za-z0-9_]+\}"/' "${files[@]}"
grep -nE "\bsed( +--[a-z-]+(=[^ ]+)?)* +['\"]" "${files[@]}" | grep -v -- '--expression'
grep -nE '\bsed\b.*--expression.*--regexp-extended' "${files[@]}"
grep -nE '; *(then|do)\b' "${files[@]}"
grep -nE '[^\\]`|^`' "${files[@]}"
grep -nE "awk[^|]*-F[^ \"']" "${files[@]}"
grep -nE
grep -nE
grep -nE | grep -vE
grep -nE
file
awk
awk
awk
This list is not exhaustive — it targets the mechanical rules most prone to slipping through. Reading remains the source of truth; treat the greps as a backstop, not a replacement.
Report
Summarize what happened: list each formatted file with a short note of the changes made, and list any paths that were skipped and why. If a file was already compliant, say so rather than editing it.