Skip to main content

cutting-a-release

Cut a new release of this monorepo. Use whenever the user wants to make, cut, ship, or prep a release, bump the version, or update RELEASE_NOTES.md / CHANGELOG.md — e.g. "let's cut a release", "make a new release", "bump the version and update the release notes", "ship the next version". Covers the release-branch workflow, the pre-merge DynamoDB GSI check, the SemVer bump + version sync, identifying what changed across the divergent main/develop histories, writing the changelog and release notes sized to the release, the squash-merge PR into main, and the required backmerge into develop.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
Boise-State-Development/agentcore-public-stack
آخر نشاط في المصدر
٣ أغسطس ٢٠٢٦ في ٠٢:١٩
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٧
التفرعات
٢٩

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
3 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
cutting-a-release
description
Cut a new release of this monorepo. Use whenever the user wants to make, cut, ship, or prep a release, bump the version, or update RELEASE_NOTES.md / CHANGELOG.md — e.g. "let's cut a release", "make a new release", "bump the version and update the release notes", "ship the next version". Covers the release-branch workflow, the pre-merge DynamoDB GSI check, the SemVer bump + version sync, identifying what changed across the divergent main/develop histories, writing the changelog and release notes sized to the release, the squash-merge PR into main, and the required backmerge into develop.
# Cutting a Release Single source of truth for cutting a release. Three concerns move together: the **branch/PR workflow**, the **version bump**, and the **two release documents** (`RELEASE_NOTES.md` + `CHANGELOG.md`). Do the steps in order, all on one `release/x.y.z` branch cut from `develop`. Detailed format templates live in reference files — load them when you reach §6: - Release-notes structure, section order, and spotlight template → [references/release-notes-format.md](references/release-notes-format.md) - Changelog structure and per-release entry skeleton → [references/changelog-format.md](references/changelog-format.md) --- ## 0. Shape of a release ``` develop (PR-only) main (PR-only, squash) │ 1. pull latest develop │ │ 2. cut release/x.y.z ────────────┐ │ bump VERSION + sync │ │ write CHANGELOG + NOTES │ │ commit + push │ │ 3. PR release/x.y.z ──▶ main (squash-merge after review) │ 4. backmerge ◀────────────────────┤ (branch off develop, merge main in, │ (PR to develop, MERGE COMMIT)│ PR into develop, MERGE COMMIT) ▼ ▼ ``` Two hard rules of this repo's branch model: 1. **Never commit directly to `develop` or `main`.** Both are PR-only — the release bump and the backmerge each land through a branch + PR. 2. **After the squash-merge into `main`, you MUST backmerge `main` → `develop`** (§7). Skipping it leaves the branches divergent and every future release conflicts on the release-artifact files. --- ## 1. Pre-merge prerequisite — the DynamoDB GSI limit **Ask before opening the release PR: does any table that ALREADY EXISTS in production gain more than one Global Secondary Index in this release?** If yes, the release **must be split into two deploys** — ship one index, wait for it to report `ACTIVE`, then ship the next. That split has to happen **before the merge**, not after a rollback. ### Why one is the limit DynamoDB's `UpdateTable` API permits **exactly one GSI creation or deletion per call**. CloudFormation issues one `UpdateTable` per changed table, so a template that adds two indexes to an existing table fails with: ``` Cannot perform more than one GSI creation or deletion in a single update ``` The limit applies to **`UpdateTable` only**. `CreateTable` accepts any number of indexes, so **a brand-new table with five GSIs is fine** — which is exactly why the question is "does an *existing* table gain more than one?" and not "are there new indexes?". The blast radius is the whole stack, not the table: when the update fails, CloudFormation rolls back **every other resource in the deploy** with it. Meanwhile `backend.yml` and `frontend-deploy.yml` are separate workflows that succeed on their own, so the release ends up running **new application code against old infrastructure**. ### Why an incremental environment cannot reveal this `dev` deploys on every merge into `develop`. Two indexes that arrive in two separate PRs therefore get **two separate platform deploys**, one index each — both succeed, and dev looks completely healthy. `main` only ever sees the *aggregate*. A release collapses a whole batch of merges into a single CloudFormation update, so the two indexes that dev applied one at a time arrive at prod **in the same `UpdateTable` call**. Only an environment that jumps a whole release at once is exposed, which means **no amount of dev soak time will surface it** and passing CI proves nothing here. Reviewing the release diff is the only place it can be caught. > **This is what took production down on 2026-08-01.** Release 1.12.0 (#814) > added `AgentDirectoryIndex` and `AgentReportsIndex` to the existing > `{prefix}-rag-assistants` table in one update. They had reached `develop` in > separate merges (`02d0f2e9`, `a128831d`) and dev had deployed each one > cleanly. The prod update failed, rolled back the rest of the deploy including a > brand-new audit-log table, and left the agent store returning 500 against > already-deployed new code. Recovery took two more patch releases. The 1.12.0 > release notes *did* flag GSI backfill timing — but not the per-update limit. ### How to check CI enforces this on every PR into `main` (`.github/workflows/gsi-update-limit.yml`). To check locally before you open the PR: ```bash node scripts/release/check-gsi-update-limit.mjs ``` It diffs `infrastructure/gsi-inventory.json` — a committed, synth-generated inventory of every DynamoDB table and its indexes — between `origin/main` and your branch, and fails when a table present in **both** needs more than one index operation. Tables that appear only on your branch are exempt (`CreateTable`), as are tables dropped entirely (`DeleteTable`). Note it counts **creations and deletions together**, because the API limit is one GSI *operation* per update: renaming an index — one add plus one drop — fails exactly like adding two does. The inventory is regenerated and verified by the infrastructure test suite, so a GSI added in code without the inventory updated fails `npx jest` first: ```bash cd infrastructure && UPDATE_GSI_INVENTORY=1 npx jest gsi-update-limit ``` > The check reports **SKIPPED** when `main` has no inventory to diff against — > true only until the first release carrying `gsi-inventory.json` lands. For that > one release, answer the question by reading the diff yourself. **The automation does not replace the question.** It compares committed inventories, so it only sees what `git` sees. Read the `gsi-inventory.json` diff in the release PR: one added line under an existing table is fine, two is a split. ### If the answer is yes — split the release Ship the indexes in separate releases, each merged and deployed on its own: 1. Release N carries **one** of the new indexes. Merge, deploy, and confirm the index reports `ACTIVE` before continuing — CloudFormation reporting `UPDATE_COMPLETE` is **not** the same as the index being usable: ```bash aws dynamodb describe-table --table-name <prefix>-rag-assistants \ --query 'Table.GlobalSecondaryIndexes[].{Name:IndexName,Status:IndexStatus}' ``` 2. Release N+1 adds the second index, once the first is `ACTIVE`. Prefer doing this **on the release branch before the merge**: drop the second index from the release branch, ship it, then restore it in the next release. ### Recovery, if it has already failed Each half needs its **own patch release** — there is no way to ship them as one PR and no way to skip the version bump. `.github/workflows/version-check.yml` fails any PR into `main` whose `VERSION` is unchanged, and it has **no label, flag, or skip path**. So a failed two-index deploy costs two patch releases (1.12.0 → 1.12.1 added the first index alone, 1.12.2 restored the second). > **Do NOT stack PR 2 on PR 1.** After PR 1 squash-merges into `main`, the > merge-base for a branch cut from PR 1 does not move. PR 1's removal and PR 2's > restoration then **cancel out in the three-dot diff GitHub shows and merges** — > the PR looks correct, CI passes, and the restore silently no-ops. The index > never comes back. > > Open PR 2 **only after PR 1 has merged**, branched fresh from `main`, as a > `git revert` of PR 1's squash commit: > > ```bash > git fetch origin main > git switch -c release/x.y.z origin/main > git revert --no-commit <pr-1-squash-sha> # restores the second index > ``` --- ## 2. Branch workflow ```bash git switch develop git pull --ff-only origin develop # release must contain all of develop git switch -c release/x.y.z # branch name == version being shipped (no v prefix) ``` The branch name **corresponds to the version being pushed** (`release/1.2.0` ships `1.2.0`) and carries **all commits currently on `develop`**. Then, on the release branch: create a task list, determine the bump (§4), bump `VERSION` + sync (§3), write both docs (§5–§6). Keep every edit on the release branch — `develop` stays untouched. Commit and push: ```bash git add VERSION backend/pyproject.toml backend/uv.lock \ frontend/ai.client/package.json frontend/ai.client/package-lock.json \ infrastructure/package.json infrastructure/package-lock.json \ CHANGELOG.md RELEASE_NOTES.md README.md git commit -m "Release/x.y.z" -m "<one-paragraph summary + bullet notes>" git push -u origin release/x.y.z ``` Open a PR `release/x.y.z` → `main`. After prerequisites (§1) + code review pass, it is **squash-merged** into `main` (the version-check gate passes because `VERSION` changed). Then do the backmerge (§7). --- ## 3. Version bump Single `VERSION` file at the repo root is the source of truth. Format: `MAJOR.MINOR.PATCH[-PRERELEASE]` (SemVer 2.0), no `v` prefix. 1. Edit `VERSION`. 2. `bash scripts/common/sync-version.sh` 3. `bash scripts/common/sync-version.sh --check` → must print `[PASS]`. 4. Commit `VERSION` **and every file the sync touched, including lockfiles**. The sync script propagates the version into `backend/pyproject.toml`, `frontend/ai.client/package.json`, `infrastructure/package.json`, the `README.md` badge + "Current release" line, and **regenerates** `backend/uv.lock` and both `package-lock.json`. Run it inside the dev container (needs `uv` + `npm` at pinned versions). - **PR gate:** PRs to `main` fail CI if `VERSION` is unchanged vs `main` or the manifests are out of sync. - **CI handles the rest:** Docker image tags, AWS resource tags, health-endpoint versions, frontend version display, and the `vX.Y.Z` git tag. Just bump + sync. --- ## 4. Decide the bump — SemVer + release sizing Pick the tier from what shipped, then size the write-up to match. | Bump | When | Notes depth | |---|---|---| | **Patch** `x.y.Z` | Bug fixes, security/dep bumps, CI/CD, docs, internal refactors — no new user-facing capability | **Brief.** 2–4 sentence Highlights + compact per-category bullets + one deployment note. No spotlights, no per-layer subsections. | | **Minor** `x.Y.0` | New features, endpoints, pages, capabilities (even if flag-gated/preview) | **Deep.** Highlights + one spotlight per major feature (backend/frontend/infra/tests) + per-category bullets. | | **Major** `X.0.0` | Breaking changes, architecture shifts, migrations | **Deepest.** Above + prominent migration/upgrade section and breaking-change callouts. | Rules of thumb: don't pad a patch; don't starve a feature; size to the largest single change; turning a dark feature **on by default** or completing a preview capability counts as user-facing → **minor**. --- ## 5. Identify what changed (the divergent-history trap) `develop` is squash-merged → `main`, so **`main` and `develop` have divergent histories**; a squash collapses a whole release into one commit on `main` whose SHA matches nothing on `develop`. > **Do NOT use `git log main..develop`.** Even after a backmerge makes `main` an > ancestor of `develop`, main's *squashed* history means develop's granular > commits are never reachable from main — so `main..develop` returns the entire > project history, not the release delta. Use the previous release's cut point (or tag) as the boundary: ```bash git tag --list 'v*' --sort=-creatordate | head git log <prev-release-cut-or-tag>..develop --no-merges --reverse --format='%h %s' git log <prev-release-cut-or-tag>..develop --merges --format='%s' # PR numbers ``` Exclude commits a prior **backmerge** dragged in (the previous `Release/x.y.z` squash commit, direct-to-main hotfixes) — they aren't new work. For every non-trivial commit **read the diff, not the message** (`git show --stat <sha>`). Bucket each change: | Category | Emoji | Use for | |---|---|---| | New | 🚀 | New features, endpoints, pages, capabilities | | Improved | ✨ | Enhancements to existing features | | Fixed | 🐛 | Bug fixes | | Changed | ⚠️ | Breaking changes, removals, deprecations, migrations | | Security | 🔒 | CVE patches, CodeQL fixes, auth hardening | | Performance | ⚡ | Latency/throughput/cost wins users notice | | Infrastructure | 🏗️ | CDK/IaC changes, new AWS resources, deploy-order changes | | Dependencies | 📦 | Package upgrades (grouped in a table) | | CI/CD | 🔧 | Workflow/pipeline/tooling changes | | Docs | 📚 | Documentation worth calling out | **Include** user/operator-facing changes, bug fixes people hit, security updates, breaking changes, dependency upgrades. **Changelog-only:** minor dep bumps with no behavior change, internal test additions. **Exclude from both:** pure internal refactors, typo/comment/formatter churn. --- ## 6. Write the two documents Same pass, same categorized list. Write `CHANGELOG.md` first (factual log), then `RELEASE_NOTES.md` (narrative). Cross-check every changelog line maps to something in the notes (except the changelog-only items above). - **`CHANGELOG.md`** — [Keep a Changelog], terse, one line per change, PR-linked `(#NNN)` when known, grouped by category emoji. New version at top; omit empty categories; never invent PR numbers. Full skeleton → [references/changelog-format.md](references/changelog-format.md). - **`RELEASE_NOTES.md`** — narrative, benefit-first with technical depth. New release at top; **never modify previous entries.** Lead each item with the user/operator outcome, then the mechanism (files, endpoints, classes). Section order + spotlight template → [references/release-notes-format.md](references/release-notes-format.md). **Translate technical → benefit.** "Implemented tool-catalog caching" → "Tool admin changes now propagate to chat on the next turn (was: required a restart). Backed by a TTL-cached DynamoDB snapshot." **Link hygiene:** GitHub renders `{#custom-anchor}` heading suffixes literally — don't use them; prefer plain text over fragile intra-doc anchors. **Before finalizing:** `sync-version.sh --check` passes, and both docs lead with the new version above the untouched previous entries. --- ## 7. Backmerge `main` → `develop` (required) After the release PR squash-merges into `main`, reconcile the branches — PR-based, you cannot push directly to `develop`. ```bash git fetch origin main develop git switch develop && git merge --ff-only origin/develop git switch -c backmerge/main-into-develop-x.y.z git merge --no-commit --no-ff origin/main # preview conflicts ``` If there are conflicts they're only the release-artifact files (`VERSION`, `CHANGELOG.md`, `RELEASE_NOTES.md`, `README.md`, manifests, lockfiles) — resolve in **main's favor** (main holds the canonical post-release state; develop's copy is pre-release plus any stale `[Unreleased]`-style content that has now shipped): ```bash git checkout --theirs -- VERSION CHANGELOG.md RELEASE_NOTES.md README.md \ backend/pyproject.toml backend/uv.lock \ frontend/ai.client/package.json frontend/ai.client/package-lock.json \ infrastructure/package.json infrastructure/package-lock.json git add -A bash scripts/common/sync-version.sh --check # must PASS git commit --no-edit ``` Verify `git merge-base --is-ancestor origin/main HEAD` succeeds, push, and open a PR into `develop`. > **Merge the backmerge PR with a MERGE COMMIT, not a squash** — the point is to > make `main` a genuine ancestor of `develop`. Squashing recreates the divergence > and the conflicts return next release. (Feature branches into `develop` squash > as usual; the backmerge is the deliberate exception.) A clean auto-merge happens when `develop` never touched the release artifacts since the last reconciliation and `main` only *added* on top. Verify the result anyway (VERSION + both docs correct, feature code intact) before committing. --- ## 8. Common pitfalls - **Adding two GSIs to an existing table in one release** — `UpdateTable` allows one per call, and dev's per-merge deploys never show it (§1). Split the release. - **Stacking the second GSI PR on the first** — the three-dot diff cancels the two commits and the restore silently no-ops (§1). Revert off `main` instead. - **`git log main..develop`** returns all history, not the delta (§5) — use the prior release cut point.
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub