Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ When call render_title "{title}"
The output should equal "Add foo"
End

It "preserves a literal '&' in a substituted token (bash 5.2+ patsub_replacement)"
scope="" type="" title="Add A & B" ticket_ref="" pr_number=""
When call render_title "{title}"
The output should equal "Add A & B"
End

It "substitutes the {scope} token alone"
scope="agents" type="" title="" ticket_ref="" pr_number=""
When call render_title "{scope}"
Expand Down
7 changes: 7 additions & 0 deletions packages/agents/content/scripts/describe-change.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@

set -euo pipefail

# Treat `&` as literal in the replacement side of `${var//pat/repl}`.
# Bash 5.2+ enables `patsub_replacement` by default, which expands `&` to the
# matched text (sed-like), corrupting any caller-supplied value (e.g., a title
# containing `&`) substituted via `substitute_tokens`. The `|| true` keeps the
# script working on bash 5.1 and earlier, where the option does not exist.
shopt -u patsub_replacement 2>/dev/null || true

readonly PROG="$(basename "$0")"

scope=""
Expand Down
21 changes: 11 additions & 10 deletions packages/agents/content/skills/shell-conventions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,14 @@ Agent-specific modules live in `agents/functions/`:

## Common mistakes

| Mistake | Fix |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `set -e` alone | Use `set -euo pipefail` — `-u` catches typos in variable names, `pipefail` catches mid-pipeline failures |
| `usage()` as function name | Use `show_usage()` — functions start with verbs |
| `show_usage` always exits 1 | Accept exit code parameter: `exit "${1:-1}"` |
| `which cmd` to check availability | Use `command -v cmd` (POSIX-portable) |
| Error messages to stdout | Always `>&2` |
| Interpolating user input into `jq` | Use `jq --arg name "$value"` for safe binding |
| Hard-coded values that vary | Accept as arguments or use `readonly` defaults at the top |
| Duplicating logic across scripts | Extract to `functions/` and source it |
| Mistake | Fix |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `set -e` alone | Use `set -euo pipefail` — `-u` catches typos in variable names, `pipefail` catches mid-pipeline failures |
| `usage()` as function name | Use `show_usage()` — functions start with verbs |
| `show_usage` always exits 1 | Accept exit code parameter: `exit "${1:-1}"` |
| `which cmd` to check availability | Use `command -v cmd` (POSIX-portable) |
| Error messages to stdout | Always `>&2` |
| Interpolating user input into `jq` | Use `jq --arg name "$value"` for safe binding |
| `${var//pat/repl}` with dynamic `repl` | Add `shopt -u patsub_replacement 2>/dev/null \|\| true`; bash 5.2+ expands `&` in `repl` to the matched text |
| Hard-coded values that vary | Accept as arguments or use `readonly` defaults at the top |
| Duplicating logic across scripts | Extract to `functions/` and source it |
Loading