framework tools/git: guards reject all option-shaped values; usage_error routes usage to stderr (codex round on PR #1464)
ci/woodpecker/pr/ci Pipeline is pending

- Option-like value = any token starting with '-' followed by an
  alphanumeric (-h, -ab, --help); multi-character short clusters were
  still accepted (codex blocker). Bare '-' is reserved (future stdin).
- usage_error now prints the usage text to stderr as well (codex
  should-fix): usage output belonged to stdout only on the help path.
- 16/16 usage-contract suites green.
This commit is contained in:
2026-08-28 18:28:28 -05:00
parent d326e6572a
commit 90f982afaa
16 changed files with 65 additions and 65 deletions
@@ -40,29 +40,29 @@ EOF
# distinct from provider, credential, and verification failures (exit 1). # distinct from provider, credential, and verification failures (exit 1).
usage_error() { usage_error() {
echo "Error: $*" >&2 echo "Error: $*" >&2
usage usage >&2
} }
# Parse arguments # Parse arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--issue) -i|--issue)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ISSUE="$2" ISSUE="$2"
shift 2 shift 2
;; ;;
-a|--assignee) -a|--assignee)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ASSIGNEE="$2" ASSIGNEE="$2"
shift 2 shift 2
;; ;;
-l|--labels) -l|--labels)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LABELS="$2" LABELS="$2"
shift 2 shift 2
;; ;;
-m|--milestone) -m|--milestone)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
MILESTONE="$2" MILESTONE="$2"
shift 2 shift 2
;; ;;
@@ -25,14 +25,14 @@ usage_error() {
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--issue) -i|--issue)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ISSUE_NUMBER="$2" ISSUE_NUMBER="$2"
shift 2 shift 2
;; ;;
-b|--body|-c|--comment) -b|--body|-c|--comment)
# R1 (2026-08-28): --body is the canonical flag; -c/--comment stays # R1 (2026-08-28): --body is the canonical flag; -c/--comment stays
# a backward-compatible alias. # a backward-compatible alias.
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
COMMENT="$2" COMMENT="$2"
shift 2 shift 2
;; ;;
@@ -46,7 +46,7 @@ usage_error() {
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--issue) -i|--issue)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ISSUE_NUMBER="$2" ISSUE_NUMBER="$2"
shift 2 shift 2
;; ;;
@@ -54,12 +54,12 @@ while [[ $# -gt 0 ]]; do
# R1 (2026-08-28): --body is the canonical flag, matching # R1 (2026-08-28): --body is the canonical flag, matching
# issue-create/issue-edit/pr-create/pr-edit; -c/--comment stays a # issue-create/issue-edit/pr-create/pr-edit; -c/--comment stays a
# backward-compatible alias. # backward-compatible alias.
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
COMMENT="$2" COMMENT="$2"
shift 2 shift 2
;; ;;
-l|--login) -l|--login)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LOGIN_OVERRIDE="$2" LOGIN_OVERRIDE="$2"
shift 2 shift 2
;; ;;
@@ -85,28 +85,28 @@ EOF
# distinct from provider, credential, and verification failures (exit 1). # distinct from provider, credential, and verification failures (exit 1).
usage_error() { usage_error() {
echo "Error: $*" >&2 echo "Error: $*" >&2
usage usage >&2
} }
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-t|--title) -t|--title)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
TITLE="$2" TITLE="$2"
shift 2 shift 2
;; ;;
-b|--body) -b|--body)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
BODY="$2" BODY="$2"
shift 2 shift 2
;; ;;
-l|--labels) -l|--labels)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LABELS="$2" LABELS="$2"
shift 2 shift 2
;; ;;
-m|--milestone) -m|--milestone)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
MILESTONE="$2" MILESTONE="$2"
shift 2 shift 2
;; ;;
@@ -26,27 +26,27 @@ usage_error() {
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--issue) -i|--issue)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ISSUE_NUMBER="$2" ISSUE_NUMBER="$2"
shift 2 shift 2
;; ;;
-t|--title) -t|--title)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
TITLE="$2" TITLE="$2"
shift 2 shift 2
;; ;;
-b|--body) -b|--body)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
BODY="$2" BODY="$2"
shift 2 shift 2
;; ;;
-l|--labels) -l|--labels)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LABELS="$2" LABELS="$2"
shift 2 shift 2
;; ;;
-m|--milestone) -m|--milestone)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
MILESTONE="$2" MILESTONE="$2"
shift 2 shift 2
;; ;;
@@ -44,38 +44,38 @@ EOF
# distinct from provider, credential, and verification failures (exit 1). # distinct from provider, credential, and verification failures (exit 1).
usage_error() { usage_error() {
echo "Error: $*" >&2 echo "Error: $*" >&2
usage usage >&2
} }
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-s|--state) -s|--state)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
STATE="$2" STATE="$2"
shift 2 shift 2
;; ;;
-l|--label) -l|--label)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LABEL="$2" LABEL="$2"
shift 2 shift 2
;; ;;
-m|--milestone) -m|--milestone)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
MILESTONE="$2" MILESTONE="$2"
shift 2 shift 2
;; ;;
-a|--assignee) -a|--assignee)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ASSIGNEE="$2" ASSIGNEE="$2"
shift 2 shift 2
;; ;;
-n|--limit) -n|--limit)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LIMIT="$2" LIMIT="$2"
shift 2 shift 2
;; ;;
-r|--repo) -r|--repo)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
REPO_OVERRIDE="$2" REPO_OVERRIDE="$2"
shift 2 shift 2
;; ;;
@@ -24,14 +24,14 @@ usage_error() {
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--issue) -i|--issue)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ISSUE_NUMBER="$2" ISSUE_NUMBER="$2"
shift 2 shift 2
;; ;;
-b|--body|-c|--comment) -b|--body|-c|--comment)
# R1 (2026-08-28): --body is the canonical flag; -c/--comment stays # R1 (2026-08-28): --body is the canonical flag; -c/--comment stays
# a backward-compatible alias. # a backward-compatible alias.
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
COMMENT="$2" COMMENT="$2"
shift 2 shift 2
;; ;;
@@ -82,7 +82,7 @@ if comments:
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--issue) -i|--issue)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ISSUE_NUMBER="$2" ISSUE_NUMBER="$2"
shift 2 shift 2
;; ;;
@@ -37,11 +37,11 @@ usage_error() {
} }
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
-r|--repo) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; REPO="$2"; shift 2 ;; -r|--repo) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; REPO="$2"; shift 2 ;;
-m|--milestone) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; MILESTONE="$2"; shift 2 ;; -m|--milestone) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; MILESTONE="$2"; shift 2 ;;
-l|--label|--labels) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; LABEL="$2"; shift 2 ;; -l|--label|--labels) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; LABEL="$2"; shift 2 ;;
-L|--login) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; LOGIN="$2"; shift 2 ;; -L|--login) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; LOGIN="$2"; shift 2 ;;
-n|--limit) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; LIMIT="$2"; shift 2 ;; -n|--limit) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; LIMIT="$2"; shift 2 ;;
-h|--help) grep '^#' "$0" | sed 's/^# \?//'; exit 0 ;; -h|--help) grep '^#' "$0" | sed 's/^# \?//'; exit 0 ;;
*) usage_error "unknown option: $1" ;; *) usage_error "unknown option: $1" ;;
esac esac
@@ -21,7 +21,7 @@ TITLE=""
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-t|--title) -t|--title)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
TITLE="$2" TITLE="$2"
shift 2 shift 2
;; ;;
@@ -45,23 +45,23 @@ EOF
# distinct from provider, credential, and verification failures (exit 1). # distinct from provider, credential, and verification failures (exit 1).
usage_error() { usage_error() {
echo "Error: $*" >&2 echo "Error: $*" >&2
usage usage >&2
} }
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-t|--title) -t|--title)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
TITLE="$2" TITLE="$2"
shift 2 shift 2
;; ;;
-d|--desc) -d|--desc)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
DESCRIPTION="$2" DESCRIPTION="$2"
shift 2 shift 2
;; ;;
--due) --due)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
DUE_DATE="$2" DUE_DATE="$2"
shift 2 shift 2
;; ;;
@@ -21,7 +21,7 @@ STATE="open"
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-s|--state) -s|--state)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
STATE="$2" STATE="$2"
shift 2 shift 2
;; ;;
@@ -24,14 +24,14 @@ usage_error() {
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-n|--number) -n|--number)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
PR_NUMBER="$2" PR_NUMBER="$2"
shift 2 shift 2
;; ;;
-b|--body|-c|--comment) -b|--body|-c|--comment)
# R1 (2026-08-28): --body is the canonical flag; -c/--comment stays # R1 (2026-08-28): --body is the canonical flag; -c/--comment stays
# a backward-compatible alias. # a backward-compatible alias.
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
COMMENT="$2" COMMENT="$2"
shift 2 shift 2
;; ;;
@@ -142,44 +142,44 @@ EOF
# distinct from provider, credential, and verification failures (exit 1). # distinct from provider, credential, and verification failures (exit 1).
usage_error() { usage_error() {
echo "Error: $*" >&2 echo "Error: $*" >&2
usage usage >&2
} }
# Parse arguments # Parse arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-t|--title) -t|--title)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
TITLE="$2" TITLE="$2"
shift 2 shift 2
;; ;;
-b|--body) -b|--body)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
BODY="$2" BODY="$2"
shift 2 shift 2
;; ;;
-B|--base) -B|--base)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
BASE_BRANCH="$2" BASE_BRANCH="$2"
shift 2 shift 2
;; ;;
-H|--head) -H|--head)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
HEAD_BRANCH="$2" HEAD_BRANCH="$2"
shift 2 shift 2
;; ;;
-l|--labels) -l|--labels)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LABELS="$2" LABELS="$2"
shift 2 shift 2
;; ;;
-m|--milestone) -m|--milestone)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
MILESTONE="$2" MILESTONE="$2"
shift 2 shift 2
;; ;;
-i|--issue) -i|--issue)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ISSUE="$2" ISSUE="$2"
shift 2 shift 2
;; ;;
@@ -57,24 +57,24 @@ EOF
# distinct from provider, credential, and verification failures (exit 1). # distinct from provider, credential, and verification failures (exit 1).
usage_error() { usage_error() {
echo "Error: $*" >&2 echo "Error: $*" >&2
usage usage >&2
} }
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
-n|--number) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; PR_NUMBER="${2:-}"; shift 2 ;; -n|--number) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; PR_NUMBER="${2:-}"; shift 2 ;;
-t|--title) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; TITLE="${2:-}"; shift 2 ;; -t|--title) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; TITLE="${2:-}"; shift 2 ;;
-b|--body) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; BODY="${2:-}"; shift 2 ;; -b|--body) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; BODY="${2:-}"; shift 2 ;;
-B|--base) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; BASE_BRANCH="${2:-}"; shift 2 ;; -B|--base) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; BASE_BRANCH="${2:-}"; shift 2 ;;
--draft) --draft)
[[ "$DRAFT_MODE" != "ready" ]] || { echo "Error: --draft and --ready are mutually exclusive" >&2; exit 2; } [[ "$DRAFT_MODE" != "ready" ]] || { echo "Error: --draft and --ready are mutually exclusive" >&2; exit 2; }
DRAFT_MODE="draft"; shift ;; DRAFT_MODE="draft"; shift ;;
--ready) --ready)
[[ "$DRAFT_MODE" != "draft" ]] || { echo "Error: --draft and --ready are mutually exclusive" >&2; exit 2; } [[ "$DRAFT_MODE" != "draft" ]] || { echo "Error: --draft and --ready are mutually exclusive" >&2; exit 2; }
DRAFT_MODE="ready"; shift ;; DRAFT_MODE="ready"; shift ;;
-l|--login) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; LOGIN_OVERRIDE="${2:-}"; shift 2 ;; -l|--login) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; LOGIN_OVERRIDE="${2:-}"; shift 2 ;;
-r|--repo) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; REPO_OVERRIDE="${2:-}"; shift 2 ;; -r|--repo) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; REPO_OVERRIDE="${2:-}"; shift 2 ;;
-H|--host) [[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)"; HOST_OVERRIDE="${2:-}"; shift 2 ;; -H|--host) [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"; HOST_OVERRIDE="${2:-}"; shift 2 ;;
-h|--help) usage 0 ;; -h|--help) usage 0 ;;
*) echo "Unknown option: $1" >&2; usage ;; *) echo "Unknown option: $1" >&2; usage ;;
esac esac
@@ -55,34 +55,34 @@ usage_error() {
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-n|--number) -n|--number)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
PR_NUMBER="$2" PR_NUMBER="$2"
shift 2 shift 2
;; ;;
-a|--action) -a|--action)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
ACTION="$2" ACTION="$2"
shift 2 shift 2
;; ;;
-b|--body|-c|--comment) -b|--body|-c|--comment)
# R1 (2026-08-28): --body is the canonical flag; -c/--comment stays # R1 (2026-08-28): --body is the canonical flag; -c/--comment stays
# a backward-compatible alias. # a backward-compatible alias.
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
COMMENT="$2" COMMENT="$2"
shift 2 shift 2
;; ;;
-l|--login) -l|--login)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
LOGIN_OVERRIDE="$2" LOGIN_OVERRIDE="$2"
shift 2 shift 2
;; ;;
-r|--repo) -r|--repo)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
REPO_OVERRIDE="$2" REPO_OVERRIDE="$2"
shift 2 shift 2
;; ;;
-H|--host) -H|--host)
[[ $# -ge 2 && "$2" != --* && ! "$2" =~ ^-[a-zA-Z]$ ]] || usage_error "option $1 requires a value (option-like values are rejected)" [[ $# -ge 2 && "$2" != - && "$2" != --* && ! "$2" =~ ^-[[:alnum:]] ]] || usage_error "option $1 requires a value (option-like values are rejected; bare - is reserved)"
HOST_OVERRIDE="$2" HOST_OVERRIDE="$2"
shift 2 shift 2
;; ;;