From c03f97188f1065c21ee5012e45d13a0eaef1f6b2 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 16 Jun 2026 08:24:21 -0400 Subject: [PATCH 1/2] chore(#1328): remove orphaned root vitest.config.ts left by SDK retirement vitest.config.ts configured Vitest (not a dependency) to run .ts test files (the repo has none) rooted at ./sdk, a directory deleted when the SDK package seam was retired in #191 (ADR-0174). No npm script, workflow, or dependency references it. Also drop the now-dead sdk/src/*.test.* branch in diff-touches-shipped-paths.cjs isCiGating(), which can never match since the sdk/ tree no longer exists. Co-Authored-By: Claude Opus 4.8 --- scripts/diff-touches-shipped-paths.cjs | 16 +++++----------- vitest.config.ts | 24 ------------------------ 2 files changed, 5 insertions(+), 35 deletions(-) delete mode 100644 vitest.config.ts diff --git a/scripts/diff-touches-shipped-paths.cjs b/scripts/diff-touches-shipped-paths.cjs index da0943d60..ae27dc64c 100644 --- a/scripts/diff-touches-shipped-paths.cjs +++ b/scripts/diff-touches-shipped-paths.cjs @@ -12,11 +12,10 @@ * - package.json (always included by `npm pack`, regardless of `files`) * - every entry in package.json `files`, treated as either an exact * file match or a directory prefix (matching `npm pack` semantics). - * - CI-gating test paths: `tests/` plus - * `sdk/src//.test.` and `.spec.` variants - * — these don't ship in the tarball, but they gate the hotfix-branch - * test job. A test fixture update that aligns with a cherry-picked - * production fix MUST be pickable or CI fails on the hotfix run. + * - CI-gating test paths: `tests/` — these don't ship in the + * tarball, but they gate the hotfix-branch test job. A test fixture + * update that aligns with a cherry-picked production fix MUST be + * pickable or CI fails on the hotfix run. * #3621 — root cause of the v1.42.3 hotfix red CI. * * `package-lock.json` is intentionally NOT considered shipped — `npm pack` @@ -65,12 +64,7 @@ function loadShipPrefixes(pkgPath) { // in hotfix.yml, this lets `test(####):` fixture-alignment commits be // cherry-picked alongside their production counterparts. function isCiGating(diffPath) { - if (diffPath.startsWith('tests/')) return true; - // SDK vitest specs live next to source. Production source ships via - // sdk/dist/ (already in package.json `files`); the test files are what's - // missing from that surface. - if (diffPath.startsWith('sdk/src/') && /\.(test|spec)\.(ts|cjs|mjs|js)$/.test(diffPath)) return true; - return false; + return diffPath.startsWith('tests/'); } function isShipped(diffPath, shipPrefixes) { diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index b0200d123..000000000 --- a/vitest.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - projects: [ - { - test: { - name: 'unit', - root: './sdk', - include: ['src/**/*.test.ts'], - exclude: ['src/**/*.integration.test.ts'], - }, - }, - { - test: { - name: 'integration', - root: './sdk', - include: ['src/**/*.integration.test.ts'], - testTimeout: 120_000, - }, - }, - ], - }, -}); From 2c3b0a01b593af510fc49fe7433967723b085ec5 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 16 Jun 2026 08:28:33 -0400 Subject: [PATCH 2/2] chore(#1330): remove one-off next-branch rollout artifacts (#231 residue) rollout-next-phase1.sh, rollout-next-phase2.sh, and next-branch-files.tar.gz were the one-shot scaffolding for the May-2026 'next' integration-branch migration (#231 / ADR #230). That migration is complete (next is the default branch); the scripts are personal one-time runners (one hardcodes a local /Volumes path) and the tarball is a redundant snapshot of 5 files now committed normally. Referenced nowhere, shipped nowhere. Co-Authored-By: Claude Opus 4.8 --- next-branch-files.tar.gz | Bin 14810 -> 0 bytes rollout-next-phase1.sh | 546 --------------------------------------- rollout-next-phase2.sh | 300 --------------------- 3 files changed, 846 deletions(-) delete mode 100644 next-branch-files.tar.gz delete mode 100755 rollout-next-phase1.sh delete mode 100755 rollout-next-phase2.sh diff --git a/next-branch-files.tar.gz b/next-branch-files.tar.gz deleted file mode 100644 index 6d4a907284f2efb4750bc5670ec5051f02579965..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14810 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 AQ2+n{ diff --git a/rollout-next-phase1.sh b/rollout-next-phase1.sh deleted file mode 100755 index 338ca4585..000000000 --- a/rollout-next-phase1.sh +++ /dev/null @@ -1,546 +0,0 @@ -#!/usr/bin/env zsh -# rollout-next-phase1.sh — native zsh, runs under macOS Terminal's default shell. -# DO NOT prefix with `bash`. Run as: ./rollout-next-phase1.sh -# -# Phase 1 of the `next` integration-branch rollout. -# -# What it does (in order, on YOUR Mac, in your repo dir): -# 1. Stashes any uncommitted work on your current branch (codex/...) with a -# named stash you can recover with `git stash list` + `git stash pop`. -# 2. Switches to main, pulls --ff-only to be sure you're current. -# 3. Creates branch: chore/introduce-next-integration-branch -# 4. Unpacks the 5 new files from the tarball. -# 5. Applies the 2 edits (branch-naming.yml, CONTRIBUTING.md) programmatically -# against the clean origin/main versions — no chance of pulling in -# unrelated commits from your current codex/ branch. -# 6. Sanity-checks YAML + bash syntax. -# 7. Commits with a conventional-commit message. -# 8. Pushes the branch to origin. -# 9. Files a `type: chore` issue using gh. -# 10. Renames the ADR file XXXX-...md → -...md and replaces XXXX in -# the ADR body with the real issue number. -# 11. Amends the commit, force-pushes-with-lease. -# 12. Opens the PR against main with `Closes #` in the body. -# -# Idempotent: re-running after a failure converges. Each step checks if it's -# already done. -# -# Usage: -# cd /Volumes/Mini\ Me/Users/trekkie/projects/gsd-core -# bash /path/to/rollout-next-phase1.sh -# -# Env overrides: -# TARBALL=/path/to/next-branch-files.tar.gz (default: ./next-branch-files.tar.gz) -# REPO=open-gsd/gsd-core (default: that) -# DRY_RUN=1 (skip push, issue, PR) - -set -euo pipefail - -# ─────────────────────────────────────────────────────────── -# Config -# ─────────────────────────────────────────────────────────── -REPO="${REPO:-open-gsd/gsd-core}" -TARBALL="${TARBALL:-./next-branch-files.tar.gz}" -BRANCH="chore/introduce-next-integration-branch" -DRY_RUN="${DRY_RUN:-0}" - -# Colors for readability (no-op if not a tty) -if [ -t 1 ]; then - C_BOLD=$'\033[1m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'; C_RED=$'\033[31m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m' -else - C_BOLD=''; C_GRN=''; C_YEL=''; C_RED=''; C_DIM=''; C_RST='' -fi - -step() { echo; echo "${C_BOLD}▸ $*${C_RST}"; } -ok() { echo "${C_GRN} ✓${C_RST} $*"; } -warn() { echo "${C_YEL} ⚠${C_RST} $*"; } -die() { echo "${C_RED} ✗ $*${C_RST}" >&2; exit 1; } -note() { echo "${C_DIM} $*${C_RST}"; } - -# ─────────────────────────────────────────────────────────── -# Step 0: Sanity checks -# ─────────────────────────────────────────────────────────── -step "Sanity checks" - -[ -d .git ] || die "Not in a git repo. cd to your gsd-core checkout first." -command -v gh >/dev/null || die "gh CLI not found. Install from https://cli.github.com/" -command -v jq >/dev/null || die "jq not found. Install: brew install jq" -gh auth status >/dev/null 2>&1 || die "gh not authenticated. Run: gh auth login" -[ -f "$TARBALL" ] || die "Tarball not found at: $TARBALL (override with TARBALL=/path/to/next-branch-files.tar.gz)" - -# Verify we're pointed at the right remote. -REMOTE_URL=$(git remote get-url origin 2>/dev/null || true) -case "$REMOTE_URL" in - *"$REPO"*) ok "Remote: $REMOTE_URL" ;; - *) die "Remote 'origin' is $REMOTE_URL — expected to contain $REPO. Wrong checkout?" ;; -esac - -# Verify tarball contents look right (fail loudly if user grabbed the wrong tar). -EXPECTED_PATHS=( - "docs/branching.md" - "docs/adr/XXXX-introduce-next-integration-branch.md" - ".github/workflows/auto-backmerge.yml" - ".github/workflows/pr-target-validator.yml" - "scripts/setup-branch-protection.sh" -) -TARLIST=$(tar tzf "$TARBALL") -for p in "${EXPECTED_PATHS[@]}"; do - echo "$TARLIST" | grep -qx "$p" || die "Tarball missing expected file: $p" -done -ok "Tarball contains all 5 expected files" - -# ─────────────────────────────────────────────────────────── -# Step 1: Stash any uncommitted work (protecting rollout files from the sweep) -# ─────────────────────────────────────────────────────────── -step "Stash any uncommitted work on current branch" - -CURRENT_BR=$(git rev-parse --abbrev-ref HEAD) -note "Currently on: $CURRENT_BR" - -# git stash --include-untracked would otherwise grab the rollout script and -# tarball (they're untracked). Move them aside, stash, move them back. -# trap EXIT guarantees restore even on script failure. -ROLLOUT_STAGE=$(mktemp -d) -ROLLOUT_FILES=(rollout-next-phase1.sh rollout-next-phase2.sh next-branch-files.tar.gz) -for f in "${ROLLOUT_FILES[@]}"; do - [ -f "./$f" ] && mv "./$f" "$ROLLOUT_STAGE/" -done -restore_rollout_files() { - # (N) is zsh's nullglob qualifier — empty match expands to nothing - # instead of erroring with "no matches found". - for f in "$ROLLOUT_STAGE"/*(N); do - cp "$f" ./ 2>/dev/null || true - done - rm -rf "$ROLLOUT_STAGE" 2>/dev/null || true -} -trap restore_rollout_files EXIT - -if [ -n "$(git status --porcelain)" ]; then - STASH_MSG="pre-next-rollout-$(date +%Y%m%d-%H%M%S) (from $CURRENT_BR)" - git stash push --include-untracked --message "$STASH_MSG" >/dev/null - ok "Stashed as: $STASH_MSG" - note "Recover later with: git stash list then git stash pop " -else - ok "Working tree clean — nothing to stash" -fi - -# Bring rollout files back into the working tree NOW so subsequent retries can find them. -restore_rollout_files - -# ─────────────────────────────────────────────────────────── -# Step 2: Switch to main, pull -# ─────────────────────────────────────────────────────────── -step "Switch to main and pull" - -git fetch origin --quiet -git checkout main >/dev/null 2>&1 || die "Could not checkout main" -git pull --ff-only origin main >/dev/null -ok "main is current at $(git log -1 --format='%h %s' | head -c 80)" - -# ─────────────────────────────────────────────────────────── -# Step 3: Create or switch to rollout branch -# ─────────────────────────────────────────────────────────── -step "Create branch $BRANCH" - -# Prune any stale worktree registrations first (e.g. from a prior aborted -# attempt that left .git/worktrees/ pointing at a deleted directory). -git worktree prune 2>/dev/null || true - -if git show-ref --verify --quiet "refs/heads/$BRANCH"; then - # Detect if a worktree still claims this branch. - # NB: no `exit` in awk — that would SIGPIPE git and trip pipefail/set -e - # silently. `head -1 || true` neutralizes the SIGPIPE we inflict ourselves. - CLAIMING_WT=$( { git worktree list --porcelain 2>/dev/null || true; } | awk -v br="refs/heads/$BRANCH" ' - /^worktree / { wt=$2 } - $0 == "branch " br { print wt } - ' | head -1 || true) - if [ -n "$CLAIMING_WT" ] && [ "$CLAIMING_WT" != "$(pwd)" ]; then - die "Branch $BRANCH is held by worktree $CLAIMING_WT. Run: git worktree remove --force '$CLAIMING_WT' (or) git worktree prune then re-run." - fi - warn "Branch $BRANCH already exists locally. Switching to it." - git checkout "$BRANCH" >/dev/null - if git rev-parse --verify "origin/$BRANCH" >/dev/null 2>&1; then - warn "origin/$BRANCH already exists. Resetting to main would lose remote commits — ABORT." - die "Delete the remote branch first if you want a clean restart: gh api -X DELETE /repos/$REPO/git/refs/heads/$BRANCH" - fi - git reset --hard main >/dev/null - ok "Reset local $BRANCH to current main" -else - git checkout -b "$BRANCH" >/dev/null - ok "Created and switched to $BRANCH" -fi - -# ─────────────────────────────────────────────────────────── -# Step 4: Unpack the 5 new files -# ─────────────────────────────────────────────────────────── -step "Unpack new files from tarball" - -tar xzf "$TARBALL" -chmod +x scripts/setup-branch-protection.sh -ok "Unpacked: $(tar tzf "$TARBALL" | wc -l | tr -d ' ') files" - -# ─────────────────────────────────────────────────────────── -# Step 5: Apply the 2 edits programmatically -# ─────────────────────────────────────────────────────────── -step "Apply edit 1/2 — add 'next' to branch-naming.yml alwaysValid" - -NAMING=".github/workflows/branch-naming.yml" -if grep -q "alwaysValid = \['main', 'next', 'develop'\]" "$NAMING"; then - ok "branch-naming.yml already has 'next' in alwaysValid (idempotent skip)" -else - if ! grep -q "alwaysValid = \['main', 'develop'\]" "$NAMING"; then - die "Could not find the expected anchor in $NAMING. Maybe upstream changed it. Open the file and add 'next' manually." - fi - # BSD sed (macOS default) needs -i ''; GNU sed needs -i. Use -i.bak then remove. - sed -i.rollout-bak "s/alwaysValid = \['main', 'develop'\]/alwaysValid = ['main', 'next', 'develop']/" "$NAMING" - rm -f "$NAMING.rollout-bak" - grep -q "alwaysValid = \['main', 'next', 'develop'\]" "$NAMING" || die "sed didn't take. Aborting." - ok "branch-naming.yml updated" -fi - -step "Apply edit 2/2 — insert 'Where Do I Open My PR?' section into CONTRIBUTING.md" - -CONTRIB="CONTRIBUTING.md" -if grep -q "^## Where Do I Open My PR?" "$CONTRIB"; then - ok "CONTRIBUTING.md already has the section (idempotent skip)" -else - # Anchor must exist exactly once. - ANCHOR_COUNT=$(grep -c "^## Pull Request Guidelines\$" "$CONTRIB" || true) - [ "$ANCHOR_COUNT" -eq 1 ] || die "Expected exactly 1 '## Pull Request Guidelines' anchor in $CONTRIB, found $ANCHOR_COUNT. Insert the section manually." - - # Write section to a temp file. BSD awk (macOS default) rejects newlines in - # -v variable values, so we pass a filename instead and let awk read it. - SECTION_FILE=$(mktemp -t gsd-rollout-section.XXXXXX) - cat > "$SECTION_FILE" <<'EOF' -## Where Do I Open My PR? (Branching Model) - -GSD uses two long-lived branches: `main` (production, what's on npm `@latest`) -and `next` (integration for the upcoming release). **Almost every PR targets -`next`.** Full guide: [`docs/branching.md`](docs/branching.md). - -| Your branch | PR target | Notes | -|---|---|---| -| `feat/NNN-slug` | `next` | Default for all new features | -| `fix/NNN-slug` | `next` | Default for all bug fixes; ships in next minor or via hotfix cherry-pick | -| `chore/`, `docs/`, `refactor/`, `test/`, `perf/`, `ci/`, `revert/` | `next` | All routine work | -| `fix/critical-NNN-slug` | `main` | Production-down emergencies only; auto-back-merges to `next` | -| `release/X.Y.0` | `main` | Created by `release.yml` — don't make these by hand | -| `hotfix/X.Y.Z` | `main` | Created by `hotfix.yml` — don't make these by hand | -| Stabilization PR for an in-flight release | `release/X.Y.0` | Fix a regression found during the RC cycle | - -**Day-to-day commands:** - -```bash -git fetch origin -git checkout next -git pull --ff-only origin next -git checkout -b fix/3187-config-corruption -# ... commit, push -gh pr create --base next --repo open-gsd/gsd-core -``` - -If you target the wrong branch by accident, the `PR Target Validator` -workflow will post a comment with the one-line fix (click "Edit" by the PR -title and change the base branch — no need to recreate the PR). - -**Why this matters:** Under the old single-branch model, every PR required -rebasing onto `main` because branch protection required "up-to-date before -merging" and `main` moved on every merge. With `next` as the integration -branch and that flag disabled on `next`, concurrent PRs can merge in any -order as long as they don't conflict on the same lines. The rebase -treadmill is gone for the 95% case. - ---- -EOF - # Trailing blank line so awk emits a blank between our closing rule and - # the next H2 (CommonMark requires blank before any header). - echo "" >> "$SECTION_FILE" - - awk -v sectionfile="$SECTION_FILE" ' - /^## Pull Request Guidelines$/ && !inserted { - while ((getline line < sectionfile) > 0) print line - close(sectionfile) - inserted = 1 - } - { print } - ' "$CONTRIB" > "$CONTRIB.tmp" - mv "$CONTRIB.tmp" "$CONTRIB" - rm -f "$SECTION_FILE" - grep -q "^## Where Do I Open My PR?" "$CONTRIB" || die "awk insertion failed." - ok "CONTRIBUTING.md section inserted" -fi - -# ─────────────────────────────────────────────────────────── -# Step 6: Sanity-check YAML + bash -# ─────────────────────────────────────────────────────────── -step "Validate YAML + bash" - -for f in .github/workflows/auto-backmerge.yml \ - .github/workflows/pr-target-validator.yml \ - .github/workflows/branch-naming.yml; do - python3 -c "import yaml,sys; yaml.safe_load(open('$f'))" \ - || die "YAML invalid: $f" -done -ok "All 3 workflow YAMLs parse" - -bash -n scripts/setup-branch-protection.sh || die "Bash syntax error: setup-branch-protection.sh" -ok "setup-branch-protection.sh syntax OK" - -# ─────────────────────────────────────────────────────────── -# Step 7: Commit -# ─────────────────────────────────────────────────────────── -step "Commit" - -git add docs/branching.md \ - docs/adr/XXXX-introduce-next-integration-branch.md \ - .github/workflows/auto-backmerge.yml \ - .github/workflows/pr-target-validator.yml \ - .github/workflows/branch-naming.yml \ - CONTRIBUTING.md \ - scripts/setup-branch-protection.sh - -if git diff --cached --quiet; then - warn "Nothing to commit (already committed from a previous run?)" -else - git commit -m "chore: introduce \`next\` integration branch (Phase 1 — additive) - -Adds: - - docs/branching.md — beginner contributor guide - - docs/adr/XXXX-...md — ADR (will be renamed with issue#) - - .github/workflows/auto-backmerge.yml — disabled in Phase 1 - - .github/workflows/pr-target-validator.yml — warn-only in Phase 1 - - scripts/setup-branch-protection.sh — idempotent gh api script - -Modifies: - - .github/workflows/branch-naming.yml — recognize 'next' - - CONTRIBUTING.md — 'Where Do I Open My PR?' section - -Phase 1 is additive: nothing operational changes until Phase 2 flips -auto-backmerge.yml's if:false→true, flips pr-target-validator.yml's -WARN_ONLY→false, creates the next branch, and switches the default -branch. See the ADR for the migration plan." - ok "Committed" -fi - -# ─────────────────────────────────────────────────────────── -# Step 8: Push -# ─────────────────────────────────────────────────────────── -step "Push branch" - -if [ "$DRY_RUN" = "1" ]; then - warn "DRY_RUN=1 — skipping push" -else - git push -u origin "$BRANCH" - ok "Pushed $BRANCH" -fi - -# ─────────────────────────────────────────────────────────── -# Step 9: File the issue (or reuse if one already exists) -# ─────────────────────────────────────────────────────────── -step "File or reuse the chore issue" - -ISSUE_TITLE="chore: introduce \`next\` integration branch to end rebase treadmill" -ISSUE_BODY=$(cat <<'EOF' -## Problem - -Every contributor branch is cut from `main` and PR'd back to `main`. Combined -with branch protection's "Require branches to be up to date before merging", -this produces a rebase treadmill: every time another PR merges, every -in-flight PR demands a rebase. With ~315 unreleased changesets queued and -multiple PRs at any time, this is constant. - -## Proposed change - -Introduce `next` as a long-lived integration branch. Routine PRs target -`next`; `main` only changes on release / hotfix / emergency fix. An -auto-back-merge workflow keeps `next` aligned with `main`. - -Full design: see the ADR in this PR (`docs/adr/XXXX-introduce-next-integration-branch.md`). -Contributor-facing guide: `docs/branching.md`. - -## Scope - -This issue covers Phase 1 of the rollout (additive infrastructure — no -operational change until the workflow flags are flipped in Phase 2). - -- New: `docs/branching.md`, ADR, `auto-backmerge.yml` (disabled), - `pr-target-validator.yml` (warn-only), `setup-branch-protection.sh`. -- Modified: `branch-naming.yml` (recognize `next`), `CONTRIBUTING.md` - ("Where Do I Open My PR?" section). -- Not yet touched: `release.yml`, `hotfix.yml`, `auto-branch.yml` — these - are Phase 3, with patches inlined in the ADR. - -## Acceptance criteria - -- All 7 files land via this PR. -- CI is green. -- `docs/branching.md` renders correctly on GitHub. -- ADR filename is renamed from `XXXX-` to `-`. - -## Phase 2 follow-up (separate PR) - -After Phase 1 merges, a small follow-up PR will: -- Create the `next` branch from `main` HEAD. -- Apply branch protection via the new script. -- Switch the default branch to `next`. -- Flip `if: false` → `if: true` in `auto-backmerge.yml`. -- Flip `WARN_ONLY: 'true'` → `'false'` in `pr-target-validator.yml`. -EOF -) - -# Look for an existing open issue with this exact title to support idempotent re-runs. -EXISTING_ISSUE=$(gh issue list --repo "$REPO" --search "in:title \"$ISSUE_TITLE\"" --state open --json number --jq '.[0].number' 2>/dev/null || echo "") - -if [ -n "$EXISTING_ISSUE" ]; then - ISSUE_NUM="$EXISTING_ISSUE" - ok "Reusing existing open issue #$ISSUE_NUM" -elif [ "$DRY_RUN" = "1" ]; then - ISSUE_NUM="DRYRUN" - warn "DRY_RUN=1 — skipping issue creation; ISSUE_NUM=$ISSUE_NUM" -else - ISSUE_URL=$(echo "$ISSUE_BODY" | gh issue create \ - --repo "$REPO" \ - --title "$ISSUE_TITLE" \ - --label "type: chore" \ - --body-file -) - ISSUE_NUM=$(echo "$ISSUE_URL" | sed 's|.*/||') - ok "Filed issue #$ISSUE_NUM — $ISSUE_URL" -fi - -# ─────────────────────────────────────────────────────────── -# Step 10: Rename ADR with issue number, replace XXXX in body -# ─────────────────────────────────────────────────────────── -step "Rename ADR and substitute issue number" - -OLD_ADR="docs/adr/XXXX-introduce-next-integration-branch.md" -NEW_ADR="docs/adr/${ISSUE_NUM}-introduce-next-integration-branch.md" - -if [ -f "$OLD_ADR" ]; then - git mv "$OLD_ADR" "$NEW_ADR" - ok "Renamed: $OLD_ADR → $NEW_ADR" -elif [ -f "$NEW_ADR" ]; then - ok "ADR already renamed (idempotent skip)" -else - die "Neither $OLD_ADR nor $NEW_ADR exists. Something is off." -fi - -# Replace XXXX inside ADR with real issue number (only in the placeholder context). -if [ "$ISSUE_NUM" != "DRYRUN" ]; then - sed -i.rollout-bak "s/XXXX-introduce-next-integration-branch/${ISSUE_NUM}-introduce-next-integration-branch/g" "$NEW_ADR" - sed -i.rollout-bak "s/placeholder \`XXXX\` prefix/placeholder (now resolved to \`${ISSUE_NUM}\`)/g" "$NEW_ADR" - rm -f "$NEW_ADR.rollout-bak" - ok "Substituted XXXX → ${ISSUE_NUM} in ADR body" -fi - -# Also patch the references in the new workflow files which mention XXXX-introduce-next-integration-branch. -for f in .github/workflows/auto-backmerge.yml .github/workflows/pr-target-validator.yml docs/branching.md CONTRIBUTING.md; do - if [ -f "$f" ] && grep -q "XXXX-introduce-next-integration-branch" "$f"; then - sed -i.rollout-bak "s/XXXX-introduce-next-integration-branch/${ISSUE_NUM}-introduce-next-integration-branch/g" "$f" - rm -f "$f.rollout-bak" - ok "Updated cross-reference in $f" - fi -done - -# ─────────────────────────────────────────────────────────── -# Step 11: Amend commit + force-push -# ─────────────────────────────────────────────────────────── -step "Amend commit and force-push" - -git add -A -if git diff --cached --quiet; then - ok "No changes to amend (idempotent skip)" -else - git commit --amend --no-edit - ok "Amended commit" -fi - -if [ "$DRY_RUN" = "1" ]; then - warn "DRY_RUN=1 — skipping force-push" -else - git push --force-with-lease origin "$BRANCH" - ok "Force-pushed (with lease)" -fi - -# ─────────────────────────────────────────────────────────── -# Step 12: Open the PR (or reuse if one already exists) -# ─────────────────────────────────────────────────────────── -step "Open PR against main" - -PR_TITLE="chore: introduce \`next\` integration branch (Phase 1 — additive)" -PR_BODY=$(cat </dev/null || echo "") - -if [ -n "$EXISTING_PR" ]; then - ok "Reusing existing PR #$EXISTING_PR" - if [ "$DRY_RUN" != "1" ]; then - echo "$PR_BODY" | gh pr edit "$EXISTING_PR" --repo "$REPO" --title "$PR_TITLE" --body-file - - ok "Updated PR #$EXISTING_PR title and body" - fi -elif [ "$DRY_RUN" = "1" ]; then - warn "DRY_RUN=1 — skipping PR creation" -else - PR_URL=$(echo "$PR_BODY" | gh pr create \ - --repo "$REPO" \ - --base main \ - --head "$BRANCH" \ - --title "$PR_TITLE" \ - --body-file -) - ok "Opened PR: $PR_URL" -fi - -# ─────────────────────────────────────────────────────────── -# Done. -# ─────────────────────────────────────────────────────────── -echo -echo "${C_BOLD}${C_GRN}━━━ Phase 1 complete ━━━${C_RST}" -echo -echo "Issue: #${ISSUE_NUM}" -echo "Branch: $BRANCH" -echo "PR: $(gh pr list --repo "$REPO" --head "$BRANCH" --state open --json url --jq '.[0].url' 2>/dev/null || echo "(check gh pr list)")" -echo -echo "Next steps:" -echo " 1. Review CI on the PR. The Changeset Required check may ask for a" -echo " changeset fragment — drop one if needed:" -echo " npm run changeset -- --type Changed --pr \\" -echo " --body \"Introduce \\\`next\\\` integration branch (Phase 1 — additive infrastructure).\"" -echo " 2. Get an approval, merge." -echo " 3. Run Phase 2: bash /path/to/rollout-next-phase2.sh" -echo -echo "Recovering your stashed work on $CURRENT_BR (if you had any):" -echo " git checkout $CURRENT_BR" -echo " git stash list # find the 'pre-next-rollout-...' entry" -echo " git stash pop stash@{N}" diff --git a/rollout-next-phase2.sh b/rollout-next-phase2.sh deleted file mode 100755 index fb45bc52c..000000000 --- a/rollout-next-phase2.sh +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env zsh -# rollout-next-phase2.sh — native zsh, runs under macOS Terminal's default shell. -# DO NOT prefix with `bash`. Run as: ./rollout-next-phase2.sh -# -# Phase 2 of the `next` integration-branch rollout. -# RUN THIS ONLY AFTER THE PHASE-1 PR HAS BEEN MERGED TO main. -# -# What it does: -# 1. Pulls main (which now contains Phase 1). -# 2. Creates the `next` branch from main HEAD, pushes it. -# 3. Applies branch protection rules via the script committed in Phase 1. -# 4. Switches the repo's default branch to `next` via gh api. -# 5. Flips `if: false` → `if: true` in auto-backmerge.yml. -# 6. Flips `WARN_ONLY: 'true'` → `'false'` in pr-target-validator.yml. -# 7. Commits the flips on a small follow-up branch, opens a PR to next. -# -# Idempotent: re-running converges. Each step checks if it's already done. -# -# Usage: -# cd /Volumes/Mini\ Me/Users/trekkie/projects/gsd-core -# bash /path/to/rollout-next-phase2.sh -# -# Env overrides: -# REPO=open-gsd/gsd-core (default) -# SKIP_PROTECTION=1 (skip running setup-branch-protection.sh) -# SKIP_DEFAULT_FLIP=1 (skip switching default branch) -# DRY_RUN=1 (skip all pushes, PRs, and api writes) - -set -euo pipefail - -REPO="${REPO:-open-gsd/gsd-core}" -FLIP_BRANCH="chore/flip-next-rollout-flags" -DRY_RUN="${DRY_RUN:-0}" - -if [ -t 1 ]; then - C_BOLD=$'\033[1m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'; C_RED=$'\033[31m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m' -else - C_BOLD=''; C_GRN=''; C_YEL=''; C_RED=''; C_DIM=''; C_RST='' -fi - -step() { echo; echo "${C_BOLD}▸ $*${C_RST}"; } -ok() { echo "${C_GRN} ✓${C_RST} $*"; } -warn() { echo "${C_YEL} ⚠${C_RST} $*"; } -die() { echo "${C_RED} ✗ $*${C_RST}" >&2; exit 1; } -note() { echo "${C_DIM} $*${C_RST}"; } - -# ─────────────────────────────────────────────────────────── -# Step 0: Sanity -# ─────────────────────────────────────────────────────────── -step "Sanity checks" - -[ -d .git ] || die "Not in a git repo. cd to your gsd-core checkout." -command -v gh >/dev/null || die "gh not found. https://cli.github.com/" -gh auth status >/dev/null 2>&1 || die "gh not authenticated." - -REMOTE_URL=$(git remote get-url origin 2>/dev/null || true) -case "$REMOTE_URL" in - *"$REPO"*) ok "Remote: $REMOTE_URL" ;; - *) die "Remote 'origin' is $REMOTE_URL — expected to contain $REPO." ;; -esac - -# Protect rollout files from the stash sweep. -ROLLOUT_STAGE=$(mktemp -d) -ROLLOUT_FILES=(rollout-next-phase1.sh rollout-next-phase2.sh next-branch-files.tar.gz) -for f in "${ROLLOUT_FILES[@]}"; do - [ -f "./$f" ] && mv "./$f" "$ROLLOUT_STAGE/" -done -restore_rollout_files() { - for f in "$ROLLOUT_STAGE"/*; do - [ -e "$f" ] || continue - cp "$f" ./ 2>/dev/null || true - done - rm -rf "$ROLLOUT_STAGE" 2>/dev/null || true -} -trap restore_rollout_files EXIT - -# Stash any in-progress work before switching branches. -CURRENT_BR=$(git rev-parse --abbrev-ref HEAD) -note "Currently on: $CURRENT_BR" -if [ -n "$(git status --porcelain)" ]; then - STASH_MSG="pre-next-phase2-$(date +%Y%m%d-%H%M%S) (from $CURRENT_BR)" - git stash push --include-untracked --message "$STASH_MSG" >/dev/null - ok "Stashed: $STASH_MSG" - STASHED=1 -else - STASHED=0 -fi - -restore_rollout_files - -# ─────────────────────────────────────────────────────────── -# Step 1: Refresh main, ensure Phase 1 is present -# ─────────────────────────────────────────────────────────── -step "Switch to main and pull" - -git fetch origin --quiet -git checkout main >/dev/null -git pull --ff-only origin main >/dev/null -ok "main is current at $(git log -1 --format='%h %s' | head -c 80)" - -# Phase-1 sentinel files must be on main now. -PHASE1_FILES=( - "docs/branching.md" - ".github/workflows/auto-backmerge.yml" - ".github/workflows/pr-target-validator.yml" - "scripts/setup-branch-protection.sh" -) -for f in "${PHASE1_FILES[@]}"; do - [ -f "$f" ] || die "Phase 1 file missing on main: $f. Has the Phase 1 PR merged?" -done -ok "Phase 1 files present on main" - -# ─────────────────────────────────────────────────────────── -# Step 2: Create next branch (idempotent) -# ─────────────────────────────────────────────────────────── -step "Create or verify the next branch" - -if git ls-remote --exit-code origin next >/dev/null 2>&1; then - ok "origin/next already exists — leaving as is" -else - if [ "$DRY_RUN" = "1" ]; then - warn "DRY_RUN=1 — would create and push next from main HEAD" - else - # Create next from current main HEAD locally and push. - git checkout -b next main 2>/dev/null || git checkout next - git push -u origin next - ok "Created and pushed origin/next at $(git log -1 --format='%h')" - # Switch back to main so subsequent steps don't accidentally edit next. - git checkout main >/dev/null - fi -fi - -# ─────────────────────────────────────────────────────────── -# Step 3: Apply branch protection -# ─────────────────────────────────────────────────────────── -step "Apply branch protection to main and next" - -if [ "${SKIP_PROTECTION:-0}" = "1" ]; then - warn "SKIP_PROTECTION=1 — skipping. You will need to run this manually:" - note " bash scripts/setup-branch-protection.sh" -elif [ "$DRY_RUN" = "1" ]; then - warn "DRY_RUN=1 — would run scripts/setup-branch-protection.sh" - REPO="$REPO" DRY_RUN=1 bash scripts/setup-branch-protection.sh | head -40 || true -else - REPO="$REPO" bash scripts/setup-branch-protection.sh - ok "Branch protection applied" -fi - -# ─────────────────────────────────────────────────────────── -# Step 4: Flip default branch to next -# ─────────────────────────────────────────────────────────── -step "Switch default branch to next" - -CURRENT_DEFAULT=$(gh api "/repos/$REPO" --jq '.default_branch') -note "Current default: $CURRENT_DEFAULT" - -if [ "$CURRENT_DEFAULT" = "next" ]; then - ok "Default is already next — skip" -elif [ "${SKIP_DEFAULT_FLIP:-0}" = "1" ]; then - warn "SKIP_DEFAULT_FLIP=1 — leaving default as $CURRENT_DEFAULT" - note "To flip later: Settings → Branches → Default branch → next" -elif [ "$DRY_RUN" = "1" ]; then - warn "DRY_RUN=1 — would PATCH /repos/$REPO default_branch=next" -else - gh api -X PATCH "/repos/$REPO" -f default_branch=next >/dev/null - ok "Default branch switched to next" -fi - -# ─────────────────────────────────────────────────────────── -# Step 5 + 6: Flip the two phase-gate flags on a follow-up branch -# ─────────────────────────────────────────────────────────── -step "Flip phase-gate flags (auto-backmerge.yml + pr-target-validator.yml)" - -# Both edits need to land on a feature branch off `next`, then PR'd back. -# (We can't push directly to main anymore — branch protection forbids it.) - -# Create or switch to the flip branch off next. -git fetch origin next --quiet 2>/dev/null || true -if git show-ref --verify --quiet "refs/heads/$FLIP_BRANCH"; then - git checkout "$FLIP_BRANCH" >/dev/null - if git rev-parse --verify "origin/$FLIP_BRANCH" >/dev/null 2>&1; then - warn "origin/$FLIP_BRANCH already exists — pulling to be sure" - git pull --ff-only origin "$FLIP_BRANCH" >/dev/null || true - else - git reset --hard origin/next >/dev/null - fi -else - git checkout -b "$FLIP_BRANCH" origin/next >/dev/null -fi - -AUTO_BM=".github/workflows/auto-backmerge.yml" -VALID=".github/workflows/pr-target-validator.yml" - -# Flip auto-backmerge: `if: false` (the phase-1 gate line) → `if: true` -# The pattern is anchored by the exact comment + indentation so we don't -# accidentally hit a different `if:` line. -if grep -q " if: false" "$AUTO_BM"; then - # macOS-compatible in-place edit - sed -i.rollout-bak "s/^ if: false$/ if: true/" "$AUTO_BM" - rm -f "$AUTO_BM.rollout-bak" - grep -q " if: true" "$AUTO_BM" || die "auto-backmerge.yml flip didn't take" - ok "auto-backmerge.yml: if: false → if: true" -elif grep -q " if: true" "$AUTO_BM"; then - ok "auto-backmerge.yml already flipped (idempotent skip)" -else - die "Could not find phase-gate 'if:' line in $AUTO_BM" -fi - -# Flip validator: WARN_ONLY: 'true' → 'false' -if grep -q "WARN_ONLY: 'true'" "$VALID"; then - sed -i.rollout-bak "s/WARN_ONLY: 'true'/WARN_ONLY: 'false'/" "$VALID" - rm -f "$VALID.rollout-bak" - grep -q "WARN_ONLY: 'false'" "$VALID" || die "validator flip didn't take" - ok "pr-target-validator.yml: WARN_ONLY 'true' → 'false'" -elif grep -q "WARN_ONLY: 'false'" "$VALID"; then - ok "pr-target-validator.yml already flipped (idempotent skip)" -else - die "Could not find WARN_ONLY in $VALID" -fi - -# YAML validation -for f in "$AUTO_BM" "$VALID"; do - python3 -c "import yaml; yaml.safe_load(open('$f'))" || die "YAML invalid after flip: $f" -done -ok "YAML still valid" - -# Commit -git add "$AUTO_BM" "$VALID" -if git diff --cached --quiet; then - warn "Nothing to commit (already committed)" -else - git commit -m "chore: enable next-branch automation (Phase 2 flips) - -- auto-backmerge.yml: enable the job (was if: false in Phase 1) -- pr-target-validator.yml: enforce instead of warn-only - -These flips are the operational gate for the next-branch model. The -next branch and branch protection were created/applied before this PR." - ok "Committed flips" -fi - -# ─────────────────────────────────────────────────────────── -# Step 7: Push + open follow-up PR against next -# ─────────────────────────────────────────────────────────── -step "Push and open follow-up PR (base = next)" - -if [ "$DRY_RUN" = "1" ]; then - warn "DRY_RUN=1 — skipping push and PR" -else - git push -u origin "$FLIP_BRANCH" - ok "Pushed $FLIP_BRANCH" - - EXISTING_PR=$(gh pr list --repo "$REPO" --head "$FLIP_BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null || echo "") - PR_TITLE="chore: enable next-branch automation (Phase 2 flips)" - PR_BODY="Phase 2 follow-up to the \`next\` integration-branch rollout. - -This PR flips the two phase-gate flags that shipped inert in Phase 1: - -- \`auto-backmerge.yml\`: \`if: false\` → \`if: true\` (the back-merge job now runs on every push to main) -- \`pr-target-validator.yml\`: \`WARN_ONLY: 'true'\` → \`'false'\` (the validator now fails the check instead of just commenting) - -The \`next\` branch and branch-protection rules were created/applied out-of-band by \`scripts/rollout-next-phase2.sh\` before this PR. - -After this merges, the model is fully live. New PRs should target \`next\` (it's the default now); the validator will catch mistakes and tell contributors how to retarget." - - if [ -n "$EXISTING_PR" ]; then - echo "$PR_BODY" | gh pr edit "$EXISTING_PR" --repo "$REPO" --title "$PR_TITLE" --body-file - - ok "Updated existing PR #$EXISTING_PR" - else - PR_URL=$(echo "$PR_BODY" | gh pr create \ - --repo "$REPO" \ - --base next \ - --head "$FLIP_BRANCH" \ - --title "$PR_TITLE" \ - --body-file -) - ok "Opened PR: $PR_URL" - fi -fi - -# ─────────────────────────────────────────────────────────── -# Done -# ─────────────────────────────────────────────────────────── -echo -echo "${C_BOLD}${C_GRN}━━━ Phase 2 complete ━━━${C_RST}" -echo -echo "What's live now:" -echo " • next branch exists, branch protection applied to main + next" -echo " • Default branch: next (new PRs default to it)" -echo " • auto-backmerge.yml: enabled — will open main→next PR after each push to main" -echo " • pr-target-validator.yml: enforcing — fails the check on wrong target" -echo -echo "Follow-up PR to merge: $(gh pr list --repo "$REPO" --head "$FLIP_BRANCH" --state open --json url --jq '.[0].url' 2>/dev/null || echo "(check gh pr list)")" -echo -echo "Phase 3 (when ready, weeks-to-months out): apply the release.yml /" -echo "hotfix.yml / auto-branch.yml patches inlined in the ADR." -if [ "$STASHED" = "1" ]; then - echo - echo "Your earlier work is stashed. Recover it with:" - echo " git checkout $CURRENT_BR && git stash list && git stash pop stash@{0}" -fi