This is a Bash shell script project template managed by just. It provides a standardized structure for building high-quality Bash applications with proper testing, linting, and documentation.
When AGENTS.md and CLAUDE.md provide conflicting guidance:
- AGENTS.md (this file) takes precedence for Bash-specific rules and project conventions
- CLAUDE.md provides general guidance that applies when AGENTS.md is silent
- In case of ambiguity, prefer the more specific guidance for the task at hand
- This template targets Bash shell script projects managed by
just. bin/contains executable entry-point scripts.lib/contains reusable library functions.tests/contains Bats-core unit tests.- No framework-specific assumptions (pure Bash).
- Never use
evalor backticks; use$()for command substitution. - Always start scripts with
#!/usr/bin/env bashandset -euo pipefail. - Quote all variables to prevent word splitting:
"$variable". - Use
[[ ]]for conditionals, never[ ]. - Use
localfor all function-scoped variables. - Return explicit exit codes; document expected codes in function headers.
- Run code through
just checkbefore committing.
bash >= 4.0— associative arrays,mapfile, process substitutionshfmt >= 3.7— consistent formatting (mvdan/sh)shellcheck >= 0.9— static analysis (koalaman/shellcheck)bats-core >= 1.9— unit testing frameworkjust >= 1.0— command runnerbasher— package management (basherpm/basher)git-cliff— changelog generation
- Error handling:
- Always use
set -euo pipefailat script entry. - Use
trapfor cleanup on exit/error. - Never ignore errors; check
$?or use||for expected failures. - Define explicit error codes as constants.
- Always use
- Function design:
- Document every function with a header block.
- Use descriptive names:
process_file,validate_input. - Keep functions under 50 lines; extract helpers for complex logic.
- Prefer pure functions (no side effects) when possible.
- I/O and output:
- Use
printfoverechofor portability. - Redirect errors to stderr:
>&2. - Use
read -rto prevent backslash interpretation. - Avoid parsing
ls; use globs orfind.
- Use
- Observability:
- Use structured logging functions (
log_info,log_error,log_warn). - Include timestamps in log output.
- Use
set -xfor debug mode (controlled by flags).
- Use structured logging functions (
- Configuration:
- Use environment variables for configuration.
- Provide sensible defaults:
${VAR:-default}. - Validate required variables at startup.
- Security:
- Never hardcode secrets; use environment variables or secret managers.
- Validate all external input at system boundaries.
- Use
mktempfor temporary files; clean up withtrap.
- Modularity: Design each function so it can be tested independently with clear inputs and outputs.
- Performance: Prefer built-in Bash constructs over external commands in hot paths.
- Extensibility: Use function dispatch tables and source-based composition.
- Type Safety: Validate inputs early; fail fast with clear error messages.
- Prefer Bash built-ins (
[[ ]],(( )),${var//pattern/}) over external commands (grep,sed,awk). - Use
mapfile/readarrayfor reading files into arrays. - Avoid subshells in loops; use parameter expansion instead.
- Use
<<< "here-string"instead ofecho "string" | command. - Profile with
timeandbash -xbefore optimizing.
- Use
&for background jobs andwaitfor synchronization. - Use named pipes (
mkfifo) for inter-process communication. - Limit concurrent jobs with
xargs -Por GNUparallel. - Use
flockfor file-based locking in multi-process scripts. - Prefer sequential execution unless parallelism provides measurable benefit.
- Avoid loading entire files into variables; process line-by-line with
while read. - Use
<<<here-strings instead of pipes where possible. - Prefer
printf -v varovervar=$(printf ...)to avoid subshells. - Use
declare -Afor associative arrays; avoid linear scans. - Clean up temporary files and variables with
trap.
# Standard error codes
readonly E_SUCCESS=0
readonly E_GENERAL=1
readonly E_INVALID_ARGS=2
readonly E_FILE_NOT_FOUND=3
# Cleanup trap
cleanup() {
local exit_code=$?
rm -f "$TEMP_FILE"
exit "$exit_code"
}
trap cleanup EXIT
# Input validation
validate_input() {
local input="$1"
if [[ -z "$input" ]]; then
log_error "Input cannot be empty"
return "$E_INVALID_ARGS"
fi
}- Path handling: Always check
cdreturn value or prefer absolute paths. Usecd "${dir}" || return 1for error handling. - Conditional commands: When using
set -e, handle expected failures explicitly with|| trueor check exit codes manually:if ! command; then ... fi - File listing: Use glob patterns (
*.txt) orfind . -name "*.txt". - Output formatting: Use
printf '%s\n' "$var"instead ofecho "$var"for consistent, portable output. - Variable quoting: Always quote variables in tests:
[[ -n "$var" ]]or[[ "$a" == "$b" ]]
- Incomplete implementations: finish features before submitting.
- Large, sweeping changes: keep changes focused and reviewable.
- Mixing unrelated changes: keep one logical change per commit.
- Using
# shellcheck disablewithout justification comment.
When fixing failures, identify root cause first, then apply idiomatic fixes instead of suppressing warnings or patching symptoms.
Use outside-in development for behavior changes:
- Git Restrictions: NEVER use
git worktree. All code modifications MUST be made directly on the current branch in the existing working directory. - start with a failing Gherkin scenario under
features/, - drive implementation with failing Bats tests,
- keep example-based Bats tests as the default inner loop for named cases and edge cases,
- add property-based tests under
tests/when the rule is an invariant instead of a single named example, - treat fuzzing as conditional work for parser-like or hostile-input scripts.
After each feature or bug fix, run:
just format
just lint
just test
just checkIf any command fails, report the failure and do not claim completion.
- BDD scenarios: place Gherkin features under
features/. - Use BDD to define acceptance behavior first, then use Bats for the inner TDD loop.
- Unit tests: place in
tests/mirroring the source structure. - Keep example-based tests as the default; add property-based tests only for invariants.
- Integration tests: place in
tests/integration/. - Add tests for behavioral changes and public API changes.
- Use
setup/teardownfor test fixtures. - Use
runto capture command output and exit status.
- Documentation, comments, and commit messages must be English only.