diff --git a/.claude/skills/coordinate/scripts/coord.sh b/.claude/skills/coordinate/scripts/coord.sh index 1244693..cb82c05 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,9 @@ 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 + # 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 @@ -306,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 @@ -445,8 +460,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"