* chore: rename npm package + bin to @opengsd/gsd-core (functional) - package.json: name @opengsd/get-shit-done-redux → @opengsd/gsd-core, bin key get-shit-done-redux → gsd-core, repository/homepage/bugs URLs - package-lock.json: regenerated (npm install --package-lock-only) - tests/**, scripts/**, bin/**, .github/**, agents/**, commands/**, get-shit-done/bin/**, get-shit-done/workflows/**: applied the 4-rule replacement (scoped npm ref, GitHub repo path, bin/clone invocations) per #505 single-source refactor Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: sweep live references to @opengsd/gsd-core Update all live documentation (README.md + translations, docs/**, CONTRIBUTING.md, VERSIONING.md, SECURITY.md, CONTEXT.md, docs/CANARY.md) to reflect the renamed package and repository. Rules applied: - @opengsd/get-shit-done-redux → @opengsd/gsd-core (scoped npm name) - open-gsd/get-shit-done-redux → open-gsd/gsd-core (GitHub repo) - GSD-redux/get-shit-done-redux → open-gsd/gsd-core (stale badge org) - bare bin/clone refs → gsd-core CHANGELOG.md, docs/adr/**, docs/RELEASE-*.md, docs/research/**, and .changeset/** are preserved byte-identical. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: add negative lookbehind to slash-command regex in bug-2954 test The extractSlashReferences regex matched /gsd-core inside npm package URLs (@opengsd/gsd-core), producing a false /gsd:core command reference. Adding a negative lookbehind (?<![a-z]) excludes matches preceded by a letter, so only standalone /gsd-<cmd> and /gsd:<cmd> tokens are found. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#518): add changeset for package rename Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#518): update package-identity expectations to the renamed coordinates The rebase regenerated the seam to @opengsd/gsd-core (bin gsd-core, repo open-gsd/gsd-core). The #498 seam tests assert deriveIdentity against the REAL package.json, so their expected literals must follow the rename. The drift-lint unit test is left as-is — its SEAM is a self-consistent fixture and its stale-literal detection cases would shift if altered; the live-repo scan in it already passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
237 lines
7.0 KiB
Bash
Executable File
237 lines
7.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# setup-branch-protection.sh
|
|
#
|
|
# Apply branch protection rules to `main` and `next` for the GSD repo.
|
|
# Idempotent — run as many times as you like. Re-running brings the live
|
|
# rules back to what this script declares, so the script IS the source of
|
|
# truth for branch protection.
|
|
#
|
|
# Usage:
|
|
# bash scripts/setup-branch-protection.sh # apply both
|
|
# bash scripts/setup-branch-protection.sh main # apply only main
|
|
# bash scripts/setup-branch-protection.sh next # apply only next
|
|
# DRY_RUN=1 bash scripts/setup-branch-protection.sh # show payloads, don't apply
|
|
#
|
|
# Requirements:
|
|
# - gh CLI authenticated against open-gsd/gsd-core with admin scope
|
|
# - jq installed
|
|
#
|
|
# What it sets:
|
|
#
|
|
# main (strict — production):
|
|
# - 2 required approving reviews
|
|
# - dismiss stale reviews on push
|
|
# - require code-owner review when CODEOWNERS applies
|
|
# - all required status checks must pass (defined in REQUIRED_CHECKS_MAIN below)
|
|
# - require branches to be up to date before merging (ON — `main` is production)
|
|
# - require linear history (OFF — release back-merges use merge commits)
|
|
# - require conversation resolution
|
|
# - require signed commits
|
|
# - block force-push and deletion
|
|
# - admins included
|
|
#
|
|
# next (loose — integration):
|
|
# - 1 required approving review
|
|
# - dismiss stale reviews on push
|
|
# - require code-owner review when CODEOWNERS applies
|
|
# - all required status checks must pass (defined in REQUIRED_CHECKS_NEXT below)
|
|
# - require branches to be up to date before merging (OFF — this is the whole point)
|
|
# - require linear history (OFF — auto-backmerge from main needs merge commits
|
|
# to preserve the link from next's history to main's release tags;
|
|
# feature PRs still squash-merge by repo merge-strategy setting)
|
|
# - require conversation resolution
|
|
# - require signed commits (OFF on next — easier for contributors)
|
|
# - block force-push and deletion
|
|
# - admins included
|
|
#
|
|
# See: docs/adr/XXXX-introduce-next-integration-branch.md
|
|
# See: docs/branching.md
|
|
|
|
set -euo pipefail
|
|
|
|
REPO="${REPO:-open-gsd/gsd-core}"
|
|
DRY_RUN="${DRY_RUN:-0}"
|
|
|
|
# Required status checks. Adjust as your CI suite evolves.
|
|
# The names must match the JOB NAME (not the workflow name) that GitHub
|
|
# records — check existing PRs to confirm.
|
|
REQUIRED_CHECKS_MAIN=(
|
|
"test"
|
|
"install-smoke"
|
|
"security-scan"
|
|
"Changeset Required / changeset-lint"
|
|
"Docs Required / docs-lint"
|
|
"PR Gate / size-check"
|
|
"Validate Branch Name / check-branch"
|
|
)
|
|
|
|
REQUIRED_CHECKS_NEXT=(
|
|
"test"
|
|
"PR Gate / size-check"
|
|
"Validate Branch Name / check-branch"
|
|
"Changeset Required / changeset-lint"
|
|
"Docs Required / docs-lint"
|
|
"PR Target Validator / validate-target"
|
|
)
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || {
|
|
echo "ERROR: missing required command: $1" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
require_cmd gh
|
|
require_cmd jq
|
|
|
|
verify_auth() {
|
|
if ! gh auth status >/dev/null 2>&1; then
|
|
echo "ERROR: gh CLI is not authenticated. Run 'gh auth login' first." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
build_payload() {
|
|
local branch="$1"
|
|
shift
|
|
local checks_array=("$@")
|
|
|
|
# Branch-specific knobs.
|
|
local approvals require_up_to_date linear_history signed_commits
|
|
case "$branch" in
|
|
main)
|
|
approvals=2
|
|
require_up_to_date=true
|
|
linear_history=false
|
|
signed_commits=true
|
|
;;
|
|
next)
|
|
approvals=1
|
|
require_up_to_date=false
|
|
# linear_history=false: auto-backmerge from main needs merge commits to
|
|
# preserve the link to release tags. Feature PRs still produce one
|
|
# commit each via repo-level "squash and merge" default — that gives
|
|
# us a clean log without enforcing linearity at the protection layer.
|
|
linear_history=false
|
|
signed_commits=false
|
|
;;
|
|
*)
|
|
echo "ERROR: unknown branch '$branch'" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Build the contexts array via jq for safe quoting.
|
|
local contexts_json
|
|
contexts_json=$(printf '%s\n' "${checks_array[@]}" | jq -R . | jq -s .)
|
|
|
|
jq -n \
|
|
--argjson contexts "$contexts_json" \
|
|
--argjson approvals "$approvals" \
|
|
--argjson require_up_to_date "$require_up_to_date" \
|
|
--argjson linear_history "$linear_history" \
|
|
--argjson signed_commits "$signed_commits" \
|
|
'{
|
|
required_status_checks: {
|
|
strict: $require_up_to_date,
|
|
contexts: $contexts
|
|
},
|
|
enforce_admins: true,
|
|
required_pull_request_reviews: {
|
|
dismiss_stale_reviews: true,
|
|
require_code_owner_reviews: true,
|
|
required_approving_review_count: $approvals,
|
|
require_last_push_approval: false
|
|
},
|
|
restrictions: null,
|
|
required_linear_history: $linear_history,
|
|
allow_force_pushes: false,
|
|
allow_deletions: false,
|
|
required_conversation_resolution: true,
|
|
required_signatures: $signed_commits,
|
|
lock_branch: false,
|
|
allow_fork_syncing: true
|
|
}'
|
|
}
|
|
|
|
apply_protection() {
|
|
local branch="$1"
|
|
local checks_var_name
|
|
if [ "$branch" = "main" ]; then
|
|
checks_var_name="REQUIRED_CHECKS_MAIN"
|
|
else
|
|
checks_var_name="REQUIRED_CHECKS_NEXT"
|
|
fi
|
|
|
|
# Expand the array indirectly (bash 3 compatible — macOS default).
|
|
eval "local checks=(\"\${${checks_var_name}[@]}\")"
|
|
|
|
local payload
|
|
payload=$(build_payload "$branch" "${checks[@]}")
|
|
|
|
echo "──────────────────────────────────────────"
|
|
echo "Branch: $branch"
|
|
echo "Required checks (${#checks[@]}):"
|
|
printf ' - %s\n' "${checks[@]}"
|
|
echo "──────────────────────────────────────────"
|
|
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
echo "[DRY RUN] Would PUT to /repos/${REPO}/branches/${branch}/protection:"
|
|
echo "$payload" | jq .
|
|
return 0
|
|
fi
|
|
|
|
echo "Applying branch protection..."
|
|
echo "$payload" | gh api \
|
|
-X PUT \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
--input - \
|
|
"/repos/${REPO}/branches/${branch}/protection" \
|
|
>/dev/null
|
|
echo "✓ Protection rules applied to $branch."
|
|
}
|
|
|
|
ensure_branch_exists() {
|
|
local branch="$1"
|
|
if ! gh api "/repos/${REPO}/branches/${branch}" >/dev/null 2>&1; then
|
|
echo "ERROR: branch '$branch' does not exist in $REPO." >&2
|
|
if [ "$branch" = "next" ]; then
|
|
cat <<EOF >&2
|
|
|
|
Create the next branch first:
|
|
git checkout main && git pull --ff-only
|
|
git checkout -b next && git push -u origin next
|
|
|
|
Then re-run this script.
|
|
EOF
|
|
fi
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
verify_auth
|
|
|
|
local targets=()
|
|
if [ $# -eq 0 ]; then
|
|
targets=(main next)
|
|
else
|
|
targets=("$@")
|
|
fi
|
|
|
|
for branch in "${targets[@]}"; do
|
|
if [ "$branch" != "main" ] && [ "$branch" != "next" ]; then
|
|
echo "ERROR: unsupported branch '$branch'. Use 'main' or 'next'." >&2
|
|
exit 1
|
|
fi
|
|
ensure_branch_exists "$branch"
|
|
apply_protection "$branch"
|
|
done
|
|
|
|
echo ""
|
|
echo "Done. To verify: gh api /repos/${REPO}/branches/<branch>/protection | jq ."
|
|
}
|
|
|
|
main "$@"
|