Skip to content

Commit 7cfa46b

Browse files
Lykhoydaclaude
andauthored
ci: require a changeset when shippable src changes (GH #189 post-mortem) (#192)
#188 merged a behavior fix (runFlow allowlist) to main with NO version bump, so it was undeliverable to marketplace installs and got re-reported as #189. Add a PR-only CI job that fails when scripts/cdp-bridge/src/ changes without a changeset — making the missing-bump class impossible rather than merely discouraged. - scripts/require-changeset.sh: the guard (seams: CHANGED_FILES, REPO_ROOT, BASE_REF) - scripts/test/require-changeset.test.sh: 4-case TDD regression test - ci.yml: PR-only require-changeset job + run the guard unit test in the build job Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5b7d6eb commit 7cfa46b

3 files changed

Lines changed: 125 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,30 @@ jobs:
4545
run: node --test 'test/integration/*.test.js'
4646
working-directory: scripts/cdp-bridge
4747

48+
# Runs at repo root (no working-directory) — guards the changeset CI gate.
49+
- name: Changeset-guard unit test
50+
run: bash scripts/test/require-changeset.test.sh
51+
4852
version-sync:
4953
name: Version sync check
5054
runs-on: ubuntu-latest
5155
steps:
5256
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 — Phase 134.5 SHA pin
5357
- run: bash scripts/sync-versions.sh
58+
59+
require-changeset:
60+
name: Require changeset
61+
runs-on: ubuntu-latest
62+
# PR-only: needs a base branch to diff against. Pushes to main are post-merge.
63+
if: github.event_name == 'pull_request'
64+
steps:
65+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 — Phase 134.5 SHA pin
66+
with:
67+
fetch-depth: 0
68+
# base_ref is passed via env (never interpolate ${{ }} directly into run:).
69+
- name: Require a changeset when shippable src changes
70+
env:
71+
BASE_REF_NAME: ${{ github.base_ref }}
72+
run: |
73+
git fetch origin "$BASE_REF_NAME"
74+
BASE_REF="origin/$BASE_REF_NAME" bash scripts/require-changeset.sh

scripts/require-changeset.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env bash
2+
# CI guard (GH #189 / v0.44.45 post-mortem): a PR that changes shippable MCP
3+
# source MUST include a changeset, so the change earns a version bump + release.
4+
# Without this, behavior fixes merge to main unversioned and never reach
5+
# marketplace installs — #188 shipped the runFlow fix with no bump, so users
6+
# never got it and it was re-reported as #189. Runs on pull_request; see
7+
# .github/workflows/ci.yml.
8+
#
9+
# Test seams (scripts/test/require-changeset.test.sh):
10+
# CHANGED_FILES newline-separated changed paths (overrides git diff)
11+
# REPO_ROOT where to look for .changeset/ (default: repo root)
12+
# BASE_REF git diff base when CHANGED_FILES is unset (default origin/main)
13+
set -uo pipefail
14+
15+
ROOT="${REPO_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
16+
BASE_REF="${BASE_REF:-origin/main}"
17+
# Shippable MCP source. Tests, docs, CI, and the .claude-plugin manifest (which
18+
# IS the changeset output) are intentionally excluded.
19+
WATCHED='^scripts/cdp-bridge/src/'
20+
21+
if [ -n "${CHANGED_FILES+x}" ]; then
22+
changed="$CHANGED_FILES"
23+
else
24+
changed="$(git -C "$ROOT" diff --name-only "${BASE_REF}...HEAD")"
25+
fi
26+
27+
src_changed="$(printf '%s\n' "$changed" | grep -E "$WATCHED" || true)"
28+
29+
if [ -z "$src_changed" ]; then
30+
echo "require-changeset: no shippable src changes — changeset not required."
31+
exit 0
32+
fi
33+
34+
changesets="$(find "$ROOT/.changeset" -maxdepth 1 -type f -name '*.md' ! -name 'README.md' 2>/dev/null || true)"
35+
36+
if [ -z "$changesets" ]; then
37+
echo "ERROR: this PR changes shippable source but has NO changeset:" >&2
38+
printf '%s\n' "$src_changed" | sed 's/^/ /' >&2
39+
cat >&2 <<'MSG'
40+
41+
A behavior change without a changeset ships to main unversioned and is
42+
undeliverable to marketplace installs (GH #189 / v0.44.45 post-mortem).
43+
44+
Fix: run `npx changeset`, describe the change, and commit the generated
45+
.changeset/*.md file. Docs / test / CI-only PRs do not need one.
46+
MSG
47+
exit 1
48+
fi
49+
50+
echo "require-changeset: shippable src changed AND a changeset is present — OK."
51+
printf '%s\n' "$changesets" | sed 's/^/ /'
52+
exit 0
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env bash
2+
# Regression test for require-changeset.sh — the CI guard that fails a PR which
3+
# changes shippable MCP source (scripts/cdp-bridge/src/) without a changeset.
4+
# Without this guard a behavior fix merges to main unversioned and is
5+
# undeliverable to marketplace installs (GH #189 / v0.44.45 post-mortem: #188
6+
# shipped the runFlow fix with no version bump, so users never got it).
7+
#
8+
# Run: bash scripts/test/require-changeset.test.sh
9+
10+
set -uo pipefail
11+
12+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
13+
GUARD="$SCRIPT_DIR/require-changeset.sh"
14+
15+
fail=0
16+
check() { # description expected_exit actual_exit
17+
if [ "$2" = "$3" ]; then
18+
echo "ok: $1"
19+
else
20+
echo "FAIL: $1 — expected exit $2, got $3"
21+
fail=1
22+
fi
23+
}
24+
25+
# Fake repo root with a .changeset/ dir holding only README (= "no changeset").
26+
tmp="$(mktemp -d)"
27+
trap 'rm -rf "$tmp"' EXIT
28+
mkdir -p "$tmp/.changeset"
29+
echo "# changesets readme" > "$tmp/.changeset/README.md"
30+
31+
# 1. shippable src changed, NO changeset -> MUST fail (the #188/#189 case)
32+
CHANGED_FILES=$'scripts/cdp-bridge/src/domain/maestro-validator.ts' \
33+
REPO_ROOT="$tmp" bash "$GUARD" >/dev/null 2>&1
34+
check "src change without changeset fails" 1 $?
35+
36+
# 2. shippable src changed, changeset present -> passes
37+
printf -- '---\n"rn-dev-agent-cdp": patch\n---\nfix\n' > "$tmp/.changeset/brave-lions.md"
38+
CHANGED_FILES=$'scripts/cdp-bridge/src/domain/maestro-validator.ts' \
39+
REPO_ROOT="$tmp" bash "$GUARD" >/dev/null 2>&1
40+
check "src change with changeset passes" 0 $?
41+
rm -f "$tmp/.changeset/brave-lions.md"
42+
43+
# 3. only non-shippable changes (tests / docs / CI) -> passes without a changeset
44+
CHANGED_FILES=$'scripts/cdp-bridge/test/unit/x.test.js\ndocs-site/foo.mdx\n.github/workflows/ci.yml' \
45+
REPO_ROOT="$tmp" bash "$GUARD" >/dev/null 2>&1
46+
check "non-src change without changeset passes" 0 $?
47+
48+
# 4. empty diff -> passes
49+
CHANGED_FILES="" REPO_ROOT="$tmp" bash "$GUARD" >/dev/null 2>&1
50+
check "empty diff passes" 0 $?
51+
52+
if [ "$fail" = 0 ]; then echo "ALL PASS"; else echo "FAILURES"; exit 1; fi

0 commit comments

Comments
 (0)