WCAG 2.1 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Cut an apexyard release — diff dev↔main, pick semver bump, generate CHANGELOG, open release PR, auto-tag on merge.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
실행 점수 미검증
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Generated release PR descriptions and release notes are durable, human-facing
artefacts. Read .claude/rules/writing-standard.md. Use the controlled technical writing profile:
lead with the outcome and next action, use plain concise prose, and remove empty
sections.
Standardises the dev → main release flow introduced by AgDR-0007. Reads the conventional-commit log between main and dev, proposes a semver bump, generates and writes the CHANGELOG entry, opens the release PR (dev→main), and triggers the auto-tag-on-release-pr-merge GitHub Actions workflow that tags the squash commit and creates a GitHub Release after merge. One command drives the operator from "nothing" to "PR open, ready for Rex + CEO". The tag and GitHub Release entry are created automatically by CI when the PR merges. The release PR also records a Released-From trailer with the exact dev cut-point SHA, and the changelog step warns loudly if commits inside the resolved changelog range aren't all making it into entries — both guard against the #872 changelog-truncation failure mode. Design rationale: AgDR-0076, AgDR-0094.
This skill is framework-only — it's for cutting apexyard releases, not for releasing managed projects under governance. Managed projects stay trunk-based and don't have a release-cut flow.
/release # auto-detect bump from conventional commits
/release v1.2.0 # explicit version, skip auto-detect
/release --dry-run # preview changelog + PR body without writing any files
/release --dry-run v1.2.0
Verify:
me2resh/apexyard). Refuse otherwise — this skill is framework-only.dev branch exists (git rev-parse --verify upstream/dev). Refuse if absent — adopt the dev/main model first.dev is ahead of main by ≥ 1 commit. Refuse if equal — nothing to release.If <version> arg was passed, use it (must match v\d+\.\d+\.\d+).
Otherwise auto-detect from the conventional-commit types in git log upstream/main..upstream/dev:
| Found | Bump |
|---|---|
Any commit subject starts with feat!: / feat(...)!: / <type>!: (breaking marker) | MAJOR |
Any feat: / feat(...): (and no breaking) | MINOR |
Only fix: / chore: / docs: / refactor: / test: / style: / perf: / build: / ci: (and no feat: or breaking) | PATCH |
Read the current latest tag:
PREV_TAG=$(git describe --tags --abbrev=0 upstream/main 2>/dev/null \
|| gh api repos/me2resh/apexyard/releases/latest --jq '.tag_name' 2>/dev/null \
|| echo "NONE")
Bump accordingly. Show the user:
Current latest tag: vX.Y.Z
Proposed next: vA.B.C (MINOR — N feat commits, M fix commits)
Override? [Enter to accept, or type a version like v1.3.0]
Call the helper script bin/release-changelog.sh, which encapsulates the git log + conventional-commit grouping + PR-number extraction logic and is independently tested. Capture both its stdout (the changelog) and stderr (the resolved commit range) — the count-mismatch guard below needs both:
CHANGELOG_DRAFT=$(PREV_TAG="vX.Y.Z" \
HEAD_REF="upstream/dev" \
VERSION="vA.B.C" \
DATE="$(date +%F)" \
bash bin/release-changelog.sh 2>/tmp/release-changelog-range.txt)
echo "$CHANGELOG_DRAFT"
# The script also prints the exact range it generated the changelog from —
# trailer-anchored (`<Released-From sha>..HEAD_REF`) when PREV_TAG carries a
# Released-From trailer, or the #737 sync-boundary heuristic otherwise. The
# count-mismatch guard below MUST compare against this range, not main..dev.
LOG_RANGE=$(grep -oE 'RELEASE_CHANGELOG_RANGE=.*' /tmp/release-changelog-range.txt | cut -d= -f2-)
# #1017: fail CLOSED here, not at the arithmetic below. An empty/unparseable
# $LOG_RANGE must stop the release, not silently flow into `git rev-list
# --count ""` (whose stderr never reaches $RAW_COUNT and would let the guard
# read a "0 commits, 0 gap" pass — AgDR-0104: "a gate that can't evaluate its
# precondition must block, not allow").
if [ -z "$LOG_RANGE" ]; then
echo "ERROR: could not read RELEASE_CHANGELOG_RANGE from bin/release-changelog.sh's stderr (see /tmp/release-changelog-range.txt). The count-mismatch guard has nothing to check against — do NOT proceed until this is resolved." >&2
exit 1
fi
#1076 + #1490 — a Closes bullet requires a same-repo scope AND a closing keyword in the real PR body. bin/release-changelog.sh emits - Closes #N only when the commit subject carries a recognised same-repo conventional-commit scope (fix(#1042): ...) and the PR body fetched with $RELEASE_GH pr view <PR> --repo <repo> --json body --jq .body (for subjects ending in (#<PR>)) contains a GitHub closing keyword for that same number (Closes #1042, Fixes #1042, or Resolves #1042). Do not read the squash commit body (%b): this repo uses squash_merge_commit_message=COMMIT_MESSAGES, so %b holds branch commit messages, not the PR description. A title reference alone is not enough when the fetch succeeds: a PR titled feat(#N) whose body says only Refs #N must produce no close line (#1490 — that nearly closed partially-fixed issues in v5.7.0). If gh fails for a PR, keep the pre-#1490 / dev behaviour for that commit (scoped title closes) and print a warning naming the PR — never silently drop a close because of a fetch failure. Unscoped commits, cross-repo scopes (docs(owner/repo#N): ...), and revert commits still emit no Closes line at all (#1076). Governing rule: prefer a MISSING close over a WRONG close when the PR body is readable.
The helper emits markdown to stdout in the format:
## [vA.B.C] — YYYY-MM-DD
Minor release — N features, M fixes.
### Added (feat)
- (#NN) <subject> — <short-sha>
...
### Fixed (fix)
- (#NN) <subject> — <short-sha>
### Changed (refactor / chore / docs)
- (#NN) <subject> — <short-sha>
### Breaking
- <only if breaking-marker commits exist>
### Closes
- Closes #N
- Closes #M
...
#1056 — one bullet per reference, not a comma list. GitHub's Closes keyword only auto-closes the reference immediately following it — a single Closes #N, #M, #P line only ever closed #N; everything after the first was silently inert. The generator now emits one - Closes #N bullet per reference so every one of them actually fires. #1076 + #1490 narrowed which numbers are eligible: a same-repo scoped commit whose PR body (via gh pr view) closes that number (fix(#1042): ... (#PR) + Closes #1042 in the PR body) is eligible; a scoped title with only Refs #N in the PR body is not; an unscoped commit, a cross-repo scope, and a revert commit are not. On a gh fetch failure the scoped title still closes, with a warning. A release with fewer Closes bullets than a title-only generator is expected when titles scoped issues that the PR bodies only referenced.
Immediately after generating the draft, sanity-check its entry count against the raw commit count in $LOG_RANGE — the same range bin/release-changelog.sh actually built the changelog from, captured in step 3 above. This is the cheap, always-on backstop behind the Released-From trailer (step 4) — it's what caught the v5.0.0 under-count by hand, and it stays even after the trailer exists as defence in depth against a mangled trailer or a trailer-less pre-AgDR-0094 tag.
#1002 — do not compare against upstream/main..upstream/dev. Under the release-cut model main only ever receives squash merges, so every individual dev commit stays permanently unreachable from main — main..dev grows monotonically with every release and can never shrink. Comparing the changelog's entry count against that raw, ever-growing number always false-positives (v5.2.0 cut: 402 raw commits vs. ~1-2 real entries, a "gap" of 400 on a perfectly correct changelog). $LOG_RANGE is anchored on the actual cut point (the Released-From trailer, or the #737 sync-boundary fallback) and is the range that matters:
#1017 — what this guard can (and can't) still see. RAW_COUNT and ENTRY_COUNT are now both derived from the same $LOG_RANGE, so the guard can no longer catch a truncated range the way it originally did — if $LOG_RANGE itself were too narrow, both counts would shrink together and the gap would stay small. That's an acceptable trade, not a silent regression: $LOG_RANGE's anchor is exact by construction once a Released-From trailer exists (AgDR-0094), and step 6's post-merge check now makes a missing trailer a hard failure — so the truncated-range case is closed at its source. What the guard genuinely still catches is classification drop-out inside a correct range: a commit that really is inside $LOG_RANGE but that bin/release-changelog.sh didn't turn into a changelog bullet (a subject it failed to classify, a filtering bug, etc.). The warning text below describes that condition, not the old truncated-range one — say what's actually being detected, not what the guard detected before #1012 re-anchored it.
RAW_COUNT=$(git rev-list --count "$LOG_RANGE") || {
# #1017: fail CLOSED — don't let a `git rev-list` failure (empty or
# malformed $LOG_RANGE) leave $RAW_COUNT unset/empty and the arithmetic
# below silently treat it as 0 (GAP would go negative, no warning, guard
# passes quiet on exactly the precondition-failure case it exists to catch).
echo "ERROR: 'git rev-list --count $LOG_RANGE' failed — the count-mismatch guard cannot evaluate its precondition. Do NOT proceed; fix \$LOG_RANGE (see /tmp/release-changelog-range.txt) and rerun." >&2
exit 1
}
# Count changelog entry bullets, excluding the "- Closes #N" summary bullets
# (#1002: they are a summary, not an entry, and used to be miscounted as one,
# causing an off-by-one on every run; #1056 emits one such bullet PER
# reference instead of a single comma-joined line, so `grep -v` here still
# needs to exclude ALL of them — `-c` already counts non-matching lines, so
# no change to this filter itself was needed, only to this comment).
ENTRY_COUNT=$(printf '%s\n' "$CHANGELOG_DRAFT" | grep -E '^- ' | grep -vcE '^- Closes ' || true)
[ -n "$ENTRY_COUNT" ] || ENTRY_COUNT=0
GAP=$(( RAW_COUNT - ENTRY_COUNT ))
# Tolerance: release/sync/"Merge branch" marker commits are excluded from the
# changelog BY DESIGN (bin/release-changelog.sh's classify step) — a handful
# of those is normal, not a bug. A gap much larger than that means commits
# genuinely inside $LOG_RANGE are failing to classify into changelog entries
# — NOT a truncated range (see the #1017 note above; $LOG_RANGE itself is
# anchored on the Released-From trailer or the #737 fallback either way).
TOLERANCE=5
if [ "$GAP" -gt "$TOLERANCE" ]; then
echo "⚠️ WARNING: the release range ($LOG_RANGE) has $RAW_COUNT commits but the changelog lists only $ENTRY_COUNT entries (gap: $GAP, tolerance: $TOLERANCE)."
echo " Commits inside \$LOG_RANGE are not making it into changelog entries — read 'git log $LOG_RANGE --oneline' against the draft above and find what's missing before proceeding."
fi
Immediately after the changelog draft, check whether this release carries a per-adopter migration script. This is the check docs/upgrading.md and .claude/skills/update/SKILL.md § 8b have documented since AgDR-0032 but that never actually existed here until #1105 — for 21 consecutive releases nothing flagged the omission, which is exactly how .claude/migrations/ ended up with scripts for only two of the ~30 release hops it should cover. This check is advisory only — it warns, it does not block the release. Making it a hard gate is a larger design decision than this fix covers (see Rule 13 below for why it's deliberately left as a follow-up, not shipped here).
MIGRATION_SCRIPT=".claude/migrations/${PREV_TAG}-to-vA.B.C.sh"
if [ ! -f "$MIGRATION_SCRIPT" ]; then
echo "⚠️ WARNING: no migration script found at $MIGRATION_SCRIPT."
echo " Every release should ship one — a real migration OR a no-op"
echo " placeholder — so /update's per-release migration chain"
echo " (.claude/migrations/, walked by _lib-migration-chain.sh) stays"
echo " walkable for adopters syncing across this release. See"
echo " .claude/migrations/README.md § 'Authoring a new migration'."
echo " If this release genuinely needs no adopter action, create the"
echo " no-op placeholder now (copy an existing one, e.g."
echo " v5.2.0-to-v5.3.0.sh, as the template) before opening the PR."
else
echo "✓ Migration script present: $MIGRATION_SCRIPT"
fi
After the changelog draft (and on --dry-run against upstream/dev; after step 4 against the release branch), list every line the release tip deletes from main. A release cut from dev can drop content that is still on main — the v5.6.3 sync removed two contributor rows from README.md on dev, and the v5.7.0 release then removed them from main (#1393). Call the helper, show the listing, and ask before continuing when it is non-empty:
# On --dry-run use upstream/dev. After step 4 use the release branch tip.
COMPARE_REF="${RELEASE_BRANCH:-upstream/dev}"
REMOVED_LINES=$(MAIN_REF="upstream/main" HEAD_REF="$COMPARE_REF" \
bash bin/release-list-removed-lines.sh)
if [ -n "$REMOVED_LINES" ]; then
echo "⚠️ WARNING: this release REMOVES the following lines from main:"
printf '%s\n' "$REMOVED_LINES"
echo ""
echo "These deletions ship to main when the release PR merges."
echo "Continue with the release? [y/N]"
# Stop unless the operator explicitly confirms. Do not open the PR on "N".
else
echo "✓ Release removes no lines from main."
fi
Show the draft (and both warnings, if any) and let the user edit interactively before proceeding. On --dry-run, print the draft and stop here with:
Dry run — no changes made. Remove --dry-run to execute.
Skip all of steps 4–5 on --dry-run.
# Check out the release branch from dev
git fetch upstream
# AgDR-0094: record the exact cut point NOW, while it's still knowable. This is
# the sha bin/release-changelog.sh will read back out of the NEXT release's
# PREV_TAG trailer — capture it here, not after checkout (the ref is what
# matters, not the local branch's HEAD, but capturing immediately after fetch
# keeps this unambiguous).
DEV_SHA=$(git rev-parse upstream/dev)
git checkout -b "release/vA.B.C" upstream/dev
# Write the CHANGELOG entry at the top of CHANGELOG.md
# (prepend the draft from step 3 above the previous top entry)
git add CHANGELOG.md
git commit -m "chore: release vA.B.C
- Prepend CHANGELOG section for vA.B.C
Refs #<release-ticket>
Released-From: $DEV_SHA"
# Push to upstream (not origin — release PRs target me2resh/apexyard)
git push upstream "release/vA.B.C"
#1004 — the trailer belongs in this commit message, not only the PR body. The release branch has exactly one commit, so this is the message a squash carries forward by construction — independent of how the merge is invoked. See step 6 for why the PR body copy alone used to silently lose the trailer.
#1490 — re-run the removed-lines check against the release branch before opening the PR (step 5). Step 3's dry-run compared upstream/main..upstream/dev. The release branch now also carries the CHANGELOG commit, so re-check with HEAD_REF=release/vA.B.C. If new removals appear, show them and ask again before gh pr create.
gh pr create \
--repo me2resh/apexyard \
--base main \
--head "release/vA.B.C" \
--title "release(#<release-ticket>): vA.B.C" \
--body-file /tmp/release-pr-body.md
PR body template (write to /tmp/release-pr-body.md before the gh pr create call). Interpolate $DEV_SHA (captured above) into the final line, matching the trailer already written into the branch commit in step 4. This copy is for human visibility on the PR page — this repo's squash_merge_commit_message setting is COMMIT_MESSAGES, so GitHub builds the squash commit's body from the branch's own commit messages, not this PR body, at merge time (#1004). The step-4 commit message is the authoritative copy the squash carries forward; keep this one in sync so a reviewer reading the PR sees the same trailer without having to check the branch commit:
<!-- multi-close: approved -->
## Summary
- **Releases vA.B.C** — see CHANGELOG section below for the full list of changes included in this release
- **CHANGELOG.md updated** — new section prepended at the top with grouped feat/fix/chore entries and PR refs
- **Auto-tag on merge** — `.github/workflows/auto-tag-on-release-pr-merge.yml` will tag the squash commit on main and create a GitHub Release entry automatically when this PR merges (AgDR-0076)
- **Release provenance recorded** — a `Released-From` trailer captures the exact `dev` SHA this release was cut from, so the next release's changelog range is deterministic instead of inferred (AgDR-0094, #872)
## CHANGELOG
<paste the draft from step 3>
## Migration script
- [ ] Does this release need a per-adopter migration? A migration script for this release — `.claude/migrations/${PREV_TAG}-to-vA.B.C.sh` (real migration OR no-op placeholder) — is included in this PR, and `docs/upgrading.md`'s "What each migration does" table has a matching row. See `.claude/migrations/README.md`. (Checked automatically by step 3's soft warning — see #1105.)
## Testing
1. After merge, confirm CI creates tag `vA.B.C` on `main` (check the `auto-tag-on-release-pr-merge` workflow run)
2. Verify `git describe --tags --abbrev=0 upstream/main` returns `vA.B.C`
3. Run `/release-sync vA.B.C` to sync main→dev and prevent squash divergence
4. Verify the squash commit's message carries the trailer: `git log -1 --pretty=format:'%(trailers:key=Released-From,valueonly)' vA.B.C`
Refs #<release-ticket>
---
## Glossary
| Term | Definition |
|------|------------|
| Squash merge | GitHub merges all commits on the PR branch into a single commit on main; the branch HEAD is discarded and the resulting main tip has a new SHA |
| Auto-tag | The `auto-tag-on-release-pr-merge.yml` workflow fires on `pull_request` → `closed` + `merged` for `release/v*` branches, tags `github.sha` (the squash commit), and creates a GitHub Release |
| Ancestry guard | `git merge-base --is-ancestor <sha> main` — fails if the tag would not be reachable from main, preventing a mis-placed tag like v2.3.0 |
| `/release-sync` | The mandatory follow-up skill that merges main→dev after a squash-merge release, preventing SHA divergence accumulation |
| `Released-From` trailer | A git trailer (`Key: value` in the commit message's final paragraph) recording the exact `dev` SHA this release was cut from — `bin/release-changelog.sh` reads it back for the next release's changelog range (AgDR-0094) |
Released-From: $DEV_SHA
Why the trailer sits after the Glossary, as its own final paragraph: git interpret-trailers (and the %(trailers:...) pretty-format used by bin/release-changelog.sh) only recognises a trailer block when it is the LAST paragraph of the message — a blank line before it, nothing but Key: value lines after it. Putting Released-From: anywhere earlier (e.g. inside the Summary or Testing sections) would make it invisible to the reader on the next release cut. Do not add anything below the trailer line.
PR title format (release is whitelisted in pr.title_type_whitelist since #168):
release(#<release-ticket>): vA.B.C
The release PR runs through the normal flow:
/code-review/approve-mergemain/release does not auto-merge. The CEO retains the discrete moment. The tag and GitHub Release are created automatically by the auto-tag-on-release-pr-merge.yml CI workflow after the merge.
#1004 — merge with an explicit subject + body, not a bare gh pr merge --squash. This repo has squash_merge_commit_message=COMMIT_MESSAGES (gh api repos/me2resh/apexyard --jq '.squash_merge_commit_message'), so GitHub's default squash body is built from the release branch's own commit messages, not the PR body. Step 4 already writes the Released-From trailer into the branch's sole commit, so a bare gh pr merge --squash would, in the common case, still carry the trailer through by construction. Don't rely on that alone — pass the reviewed PR body explicitly instead, so the merged commit is guaranteed to match what Rex and the CEO actually reviewed, independent of repo settings, a stray fixup commit changing the branch's commit count, or a future change to squash_merge_commit_message:
gh pr merge <pr-number> --repo me2resh/apexyard --squash \
--subject "release(#<release-ticket>): vA.B.C" \
--body-file /tmp/release-pr-body.md
/tmp/release-pr-body.md is the same file written in step 5 — its final paragraph is already the Released-From trailer, so this is the one merge command that keeps the trailer, the changelog, and the reviewed content all in sync on the squash commit.
Merging through /approve-merge instead of this raw command (#1136, AgDR-0132): /approve-merge is the mandated human-only merge path (#1042) — in practice this step's merge almost always runs there, not as the raw gh pr merge shown above. /approve-merge now detects a release-class PR (head branch release/vA.B.C, or title starting release() the same way it already detects a sync-class PR, and automatically passes --subject/--body-file built from the PR's own title/body — so it produces the same trailer-preserving squash this rule describes, without the operator needing to run the raw command by hand. See .claude/skills/approve-merge/SKILL.md step 6. The raw command above stays documented as the fallback for a manual merge outside /approve-merge (e.g. tracker.kind=none, or the CEO merging directly on the GitHub UI/CLI).
Why not just change the repo's squash_merge_commit_message setting to PR_BODY? Considered and rejected: that's a repo-wide default that would change the squash body of every PR merged to this repo, not just releases — the least-targeted option from #1004's own candidate list. The explicit --subject/--body-file flags above override the default only for this one merge call, which is exactly the blast radius this fix needs.
Verify immediately after merge — do not skip:
git fetch upstream main
TRAILER=$(git log -1 --pretty=format:'%(trailers:key=Released-From,valueonly)' upstream/main)
if [ -z "$TRAILER" ]; then
echo "ERROR: squash commit on main has NO Released-From trailer." >&2
echo "The next release's changelog range will silently fall back to the" >&2
echo "#737 sync-boundary heuristic. Do not proceed to /release-sync until" >&2
echo "this is understood — check what merge command was actually used." >&2
exit 1
fi
echo "Released-From trailer confirmed: $TRAILER"
This is the loud-failure the trailer mechanism needs (#1004) — a missing trailer is otherwise invisible until the next release cut mis-anchors its range.
When the release PR is squash-merged to main, the .github/workflows/auto-tag-on-release-pr-merge.yml workflow fires automatically:
release/vA.B.C → vA.B.C).github.sha (the squash commit SHA — already the correct commit on main).git merge-base --is-ancestor <sha> main.git push origin --tags.No manual tagging required after merge. The workflow handles it.
If the auto-tag workflow fails for any reason, follow the manual steps:
# 1. Fetch so upstream/main points at the squash commit.
git fetch upstream
# 2. Tag the tip of upstream/main (the squash commit, NOT the branch HEAD).
git tag vA.B.C upstream/main
# 3. Ancestry guard before pushing.
if ! git merge-base --is-ancestor vA.B.C upstream/main; then
echo "ERROR: tag is mis-placed — delete and re-tag." >&2
exit 1
fi
# 4. Push the tag (use --tags, not the bare tag name, to avoid the
# branch-name validator hook misfiring on tag-push commands).
git push upstream --tags
Verify all four assertions hold (the first three: CI workflow also checks these; the fourth: step 6's verification above, repeated here so it isn't lost if step 6 was skipped):
git merge-base --is-ancestor vA.B.C upstream/main exits 0git describe --tags --abbrev=0 upstream/main returns vA.B.Chttps://github.com/me2resh/apexyard/releases/tag/vA.B.Cgit log -1 --pretty=format:'%(trailers:key=Released-From,valueonly)' vA.B.C is non-empty (#1004) — if empty, STOP before running /release-sync; the next release's changelog range will silently degrade to the sync-boundary heuristicReleased vA.B.C — auto-tag workflow running on CI, will tag main + create GitHub Release.
N tickets auto-closed via the release PR.
Drift banner on adopters' forks will fire on next session.
Next: /release-sync vA.B.C
Squash-merging dev→main creates SHA divergence: the squash commit on main is absent from dev, causing the next release PR to accumulate conflicts. Every release must be followed immediately by a sync-back PR.
Invoke:
/release-sync vA.B.C
This files a sync/main-to-dev-after-vA.B.C → dev PR that merges upstream/main into upstream/dev with -X ours, making the squash commit an ancestor of dev. The skill is idempotent — if main and dev are already in sync it exits 0 without creating a PR.
Do not skip this step. The v2.0.0 release suffered 99 merge conflicts because accumulated sync-back skips were not addressed for multiple release cycles (#403).
Do these steps after /release-sync merges. No hook enforces them. Skip none.
An outside reporter is a person who is not a maintainer account (me2resh, atlas-apex) and who opened an issue.
me2resh/apexyard-site, plan plan-release-sync).Step 4 matters. The v5.10.0 release left the site on v5.6.3.
github.sha (the squash commit). The manual fallback similarly tags upstream/main. See step 7 for the full guard.<!-- multi-close: approved --> in the release PR body is required — release PRs legitimately close many tickets at once.--dry-run stops before writing any files. The draft CHANGELOG section and PR body are shown; nothing is committed, branched, pushed, or filed.Released-From trailer must be its own final paragraph in BOTH the step-4 branch commit and the step-5 PR body (AgDR-0094). It is what makes the next release's changelog range deterministic — a trailer that lands mid-body (or gets pushed off the end by later edits) silently degrades back to the pre-AgDR-0094 sync-boundary heuristic, with all its known failure modes (#737, #872).$LOG_RANGE's raw commit count and the changelog's entry count means commits inside that range didn't make it into changelog entries (#1017 — not a truncated range; both counts derive from the same $LOG_RANGE, so a truncated range can no longer produce this gap). Don't proceed past it without the operator explicitly confirming the range is correct. Never compare against upstream/main..upstream/dev (#1002) — under the release-cut squash model that range only ever grows, so it always false-positives; $LOG_RANGE (captured in step 3) is the range the changelog was actually built from. A $LOG_RANGE that fails to resolve or fails git rev-list must halt the release (exit 1), never pass silently (#1017) — see step 3's guard.--subject/--body-file, never a bare gh pr merge --squash (#1004) — this repo's squash_merge_commit_message=COMMIT_MESSAGES setting silently drops a trailer that lives only in the PR body. See step 6. /approve-merge — the actual merge path for #1042 — applies this automatically for a release-class PR (#1136, AgDR-0132); the raw command in step 6 is the fallback for a manual merge outside /approve-merge.Released-From trailer landed on the squash commit immediately after merge, and stop before /release-sync if it didn't (#1004) — a missing trailer is invisible until the next release cut silently mis-anchors its changelog range. See step 6's verification block and the post-tag checklist in step 7.bin/release-list-removed-lines.sh against upstream/main..$COMPARE_REF (dev on dry-run, the release branch after step 4). A non-empty listing is a stop-and-ask, not a silent proceed: that is how the v5.7.0 cut dropped contributor rows that still lived on main.AgDR-0007 — the release-cut branch model this skill enactsAgDR-0076 — the automation design record (this enhancement)AgDR-0094 — release provenance via the Released-From trailer + count-mismatch guard (#872)AgDR-0197 — close only from PR-body keywords + list lines a release removes from main (#1490)bin/release-changelog.sh — the changelog generation helper script, independently testedbin/release-list-removed-lines.sh — lists lines the release tip deletes from main (#1490)docs/release-process.md — the prose runbook (this skill is the automation; the doc is the manual fallback).github/workflows/auto-tag-on-release-pr-merge.yml — the CI workflow that tags the squash commit after mergegolden-paths/pipelines/auto-tag-on-release-pr-merge.yml — the reusable template for managed projects.claude/skills/update/SKILL.md — the inverse skill, used by adopters pulling new releases into their fork.claude/skills/release-sync/SKILL.md — the mandatory follow-up skill that syncs main back to dev after every release, preventing squash-divergence accumulationPart of ApexYard — multi-project SDLC framework for Claude Code · MIT.
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
WCAG 2.1 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
WCAG 2.2 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Browse / search / show / stats AgDRs across the portfolio — recalls "have we decided this before?".
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Analytics audit — SDK config, event naming, funnel completeness, dashboards. Deep-dive for /launch-check analytics.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Record per-PR architecture-review approval for design-artifact PRs (required by the architecture gate). ONLY on an explicit per-PR architect "approved".
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Record per-PR design-review approval (UI merge gate). ONLY on an explicit per-PR designer "approved".
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.