From 47035230a1a58f364f2bcf051949d63c3eb44d34 Mon Sep 17 00:00:00 2001 From: m-vawter Date: Fri, 2 Oct 2026 20:18:55 -0400 Subject: [PATCH 1/3] fix(coord): make coord.sh's scope checks work on macOS and with glob scopes claim: compare Files entries as exact strings (grep -qxF, like coord.ps1's -contains) and quote the lists so entries are never glob-expanded. macOS BSD grep rejected a glob entry such as tests/** as a regex ('repetition-operator operand invalid'), which aborted the claim. review: check PR paths against Files entries the way coord.ps1 does (wildcard either way, or a prefix match with trailing '*' and '/' trimmed) via a new in_scope helper. The old substring test reported every path under a glob scope as undeclared. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/coordinate/scripts/coord.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.claude/skills/coordinate/scripts/coord.sh b/.claude/skills/coordinate/scripts/coord.sh index 1244693..7834a1f 100644 --- a/.claude/skills/coordinate/scripts/coord.sh +++ b/.claude/skills/coordinate/scripts/coord.sh @@ -74,6 +74,20 @@ files_of() { # issue grep -v '^$' || true } +in_scope() { # path, newline-separated Files entries -> true if an entry covers the path + # Mirrors coord.ps1: a wildcard match either way, or a prefix match on the + # entry with trailing '*' and '/' trimmed. String matching only (no regex, no + # pathname expansion), so it behaves the same with BSD and GNU tools. + local p="$1" a pre + while IFS= read -r a; do + [ -n "$a" ] || continue + pre="${a%"${a##*[!*]}"}"; pre="${pre%"${pre##*[!/]}"}" + # shellcheck disable=SC2053 # unquoted right-hand sides: glob matching on purpose + if [[ $p == $a || $a == $p || $p == "$pre"* ]]; then return 0; fi + done <<< "$2" + return 1 +} + # Issue numbers referenced as #N anywhere in a body (unique, first-ref order). referenced_issues() { # body printf '%s' "$1" | grep -o '#[0-9]\+' | tr -d '#' | awk '!seen[$0]++' || true @@ -292,8 +306,8 @@ cmd_claim() { [ -n "$o" ] || continue their="$(files_of "$o")" [ -n "$their" ] || continue - clash="$(printf '%s\n' $scope | sort -u | while read -r p; do - printf '%s\n' $their | grep -qx "$p" && echo "$p" + clash="$(printf '%s\n' "$scope" | sort -u | while IFS= read -r p; do + printf '%s\n' "$their" | grep -qxF -- "$p" && echo "$p" done)" [ -n "$clash" ] || continue if gh issue view "$o" --repo "$REPO" --json body --jq '.body // ""' | grep -q '^split:'; then @@ -445,8 +459,8 @@ cmd_review() { if [ -n "$allowed" ]; then local undeclared p undeclared="$(gh pr view "$prn" --repo "$REPO" --json files --jq '.files[].path' | - while read -r p; do - printf '%s\n' $allowed | grep -qF "$p" || echo "$p" + while IFS= read -r p; do + in_scope "$p" "$allowed" || echo "$p" done)" if [ -n "$undeclared" ]; then die "PR #$prn touches paths outside the linked issues' Files scope: $(printf '%s' "$undeclared" | paste -sd, -) - update the issues' Files or drop the paths" From 4dc2c8a7306aae7d135ec88932ee52b368da3422 Mon Sep 17 00:00:00 2001 From: m-vawter Date: Fri, 2 Oct 2026 20:53:44 -0400 Subject: [PATCH 2/3] fix(coord): a non-overlapping last scope entry no longer aborts claim The overlap loop ended on 'grep ... && echo', so a final non-match left the command substitution with status 1, and set -e exited claim silently whenever another open issue declared Files. Use if/fi so a miss is not an error. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/coordinate/scripts/coord.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.claude/skills/coordinate/scripts/coord.sh b/.claude/skills/coordinate/scripts/coord.sh index 7834a1f..d4ac4a4 100644 --- a/.claude/skills/coordinate/scripts/coord.sh +++ b/.claude/skills/coordinate/scripts/coord.sh @@ -307,7 +307,8 @@ cmd_claim() { their="$(files_of "$o")" [ -n "$their" ] || continue clash="$(printf '%s\n' "$scope" | sort -u | while IFS= read -r p; do - printf '%s\n' "$their" | grep -qxF -- "$p" && echo "$p" + # if/fi, not &&: a final non-match must not leave status 1 for set -e to trip on. + if printf '%s\n' "$their" | grep -qxF -- "$p"; then echo "$p"; fi done)" [ -n "$clash" ] || continue if gh issue view "$o" --repo "$REPO" --json body --jq '.body // ""' | grep -q '^split:'; then From c4861ee2996a9acb8a75fece7254a65721c12382 Mon Sep 17 00:00:00 2001 From: m-vawter Date: Fri, 2 Oct 2026 20:54:22 -0400 Subject: [PATCH 3/3] fix(coord): claim's overlap loop reads issue numbers, not body lines The loop emitted 'numberbody' and read it line by line, so every line of a multi-line body was treated as an issue number ('invalid issue format' noise; a body line starting with digits would be checked as that issue). Only the number is used, so emit only the number. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/coordinate/scripts/coord.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/coordinate/scripts/coord.sh b/.claude/skills/coordinate/scripts/coord.sh index d4ac4a4..cb82c05 100644 --- a/.claude/skills/coordinate/scripts/coord.sh +++ b/.claude/skills/coordinate/scripts/coord.sh @@ -321,7 +321,7 @@ cmd_claim() { [ -n "$common" ] && continue die "#$N scope overlaps #$o on: $(printf '%s' "$clash" | paste -sd, -) - record a split on one of the issues or hand off ownership first" done < <(gh issue list --repo "$REPO" --state open --limit 100 --json number,labels,body \ - --jq '.[] | select(((.labels // []) | map(.name) | index("hub")) | not) | select(.number != '"$N"') | "\(.number)\t\(.body // "")"') + --jq '.[] | select(((.labels // []) | map(.name) | index("hub")) | not) | select(.number != '"$N"') | .number') comment "$N" CLAIM "plan: ${OPT_plan:-_(none given)_}"$'\n'"eta: ${OPT_eta:-_(none given)_}" gh issue edit "$N" --repo "$REPO" --add-assignee "@me" >/dev/null set_status "$N" claimed