CODAP v3 Build & Release
Overview
Interactive workflow for CODAP v3 releases. Guides you through Jira setup, release notes generation, version updates, PR creation, tagging, and deployment.
Quick Reference
| Phase |
Command |
Description |
| 1 |
/codap-v3-build |
Prepare release (Jira version, tag stories) |
| 2 |
/codap-v3-build notes |
Prepare release notes (interactive) |
| 3 |
/codap-v3-build files |
Update version files |
| 4 |
/codap-v3-build pr |
Create release PR |
| 5 |
/codap-v3-build tag |
Tag and create GitHub release |
| 6 |
/codap-v3-build deploy [version] |
Deploy to staging/production |
| fix |
/codap-v3-build fix {old-version} |
Revise release after staging QA failure |
Getting Started
When invoked, introduce the skill:
This skill will walk you through the process of building a release of CODAP v3. The process has 6 phases:
- Prepare the Release - Set up Jira version and gather context
- Prepare Release Notes - Interactive walkthrough to create CHANGELOG entry
- Update Version Files - Update package.json, versions.md, CHANGELOG.md
- Create Release PR - Build, capture asset sizes, create PR
- Tag and Release - After PR merge, create git tag and GitHub release
- Deploy - Stage, QA, deploy to production
Are you ready to proceed?
Wait for user confirmation before starting Phase 1.
Phase 1: Prepare the Release
Goal: Create Jira release version and gather context.
Steps
Check workspace status:
git status
- If there are modified/staged files, ask user to: Stash / Commit / Discard
- Untracked files are okay to leave
Ensure on main branch with latest:
git checkout main
git pull
Get current build number:
cat v3/build_number.json
Get previous release tag:
git tag --sort=-creatordate | head -5
Show context to user:
- Display last 3 releases (version and date)
- Show current build number from
build_number.json
Determine recommended version string:
- Use build number + 1 (build number auto-increments when release PR merges)
- Match previous release pattern (e.g.,
-beta, -pre)
- Example: If build number is
2662, recommend 3.0.0-beta.2663
Get previous release date from Jira (for start date default)
Ask user for Jira release details:
| Field |
Default |
Options |
| Version name |
Based on previous release pattern + new build number |
Match previous pattern (e.g., -beta, -rc, or release) |
| Start date |
Previous release date |
User can modify |
| Release date |
Today's date |
Today / Tomorrow / Custom future date |
| Description |
Version {version} |
User can modify |
Note: The release date chosen here is used throughout the process:
- CHANGELOG.md header date
- versions.md entry date
- Jira release date
Create Jira release version using Atlassian MCP tools (status: Unreleased)
Phase 2: Prepare Release Notes
Goal: Generate CHANGELOG entry with user-selected titles.
Steps
Get PRs since last release:
git log <last-tag>..HEAD --oneline | grep -E '\(#[0-9]+\)|Merge pull request #[0-9]+'
Note: This finds both regular merge commits AND squash-merged PRs (which include (#123) in the commit message). Using --merges alone misses squash merges.
Get PR details from GitHub:
gh pr view <number> --json number,title,headRefName
Match PRs to Jira stories by CODAP-XXX ID:
- Check branch name first (most reliable, e.g.,
CODAP-1027-inbounds-url-param)
- Then PR title (e.g.,
CODAP-1027: Implement inbounds parameter)
- Use caution with PR descriptions - they may reference related stories (e.g., "Follow-up to CODAP-XXX") that aren't the primary story for this PR
- Extract unique CODAP-XXX IDs
For each matched item, fetch:
- Jira story details (summary, issue type, current status)
- PR title from GitHub
- Generate AI-suggested title (concise, user-focused)
Interactive walkthrough for each item:
IMPORTANT - NO SHORTCUTS:
- Do NOT ask user to approve the entire list at once
- Do NOT batch items together (e.g., "approve these 3 items")
- Do NOT skip showing title options
- ALWAYS go through items ONE BY ONE, presenting all title options for each
IMPORTANT - PRESENTATION ORDER:
Present the title options table as markdown output FIRST, then use AskUserQuestion. This prevents the question UI from covering the options.
First, output this markdown:
### Item 1/8: CODAP-1027 (Story)
| Source | Title |
|--------|-------|
| **AI suggestion** | {ai_title} |
| **Jira** | {jira_summary} |
| **PR** | {pr_title} |
Note: Strip Jira IDs from PR titles before presenting (e.g., "CODAP-138: Fix point color" → "Fix point color")
Jira status notice (if not Done):
If the story's Jira status is anything other than "Done" (e.g., "In Project Team Review", "In Code Review"), append the status to the item header line with a warning indicator:
### Item 1/8: CODAP-1027 (Story) — Jira status: In Project Team Review ⚠️
Do NOT add the status suffix for stories that are "Done". The user can choose to exclude the story via the Section question.
Then ask questions using AskUserQuestion (Section and Title are TWO SEPARATE CALLS so Title is skipped if Exclude):
Section question:
- Question: "Which section for this item?"
- Options: Features / Bug Fixes / Under the Hood / Exclude
- Add "(Recommended)" to Features for Stories, Bug Fixes for Bugs
- If user types in "Other", interpret as an instruction (e.g., "go back to previous item") and handle accordingly
If Section is NOT Exclude - ask Title question:
- Question: "Which title? (See table above, or type your preferred title in 'Other')"
- Options: AI suggestion / Jira / PR (no "Custom" - user types preferred title in built-in "Other")
- If user types in "Other", use their text as the title
- Title option order must ALWAYS be: AI suggestion, Jira, PR (both in table and in question options)
- Stories included in release notes will have their Fix Version updated automatically (tracked for step 9)
If Section IS Exclude - ask Fix Version question:
- Question: "Should this story's Fix Version be set to this release?"
- Options: Yes / No
- Default recommendation: Yes (Recommended) - infrastructure improvements may not be user-facing but should still be tracked in Jira
- If Yes: Add to Fix Version update list (step 9) even though excluded from release notes
- If No: Do not update Fix Version (e.g., if the story was fixed in a prior release, or the PR isn't part of this release)
After selection, confirm:
✓ CODAP-1027 → Features: "Selected title here"
For PRs without Jira IDs:
- Show PR title only
- Default recommendation: Exclude (Recommended) for docs, dependencies, maintenance
- Option to include in Under the Hood if relevant
- No Fix Version to update (no Jira story)
Generate CHANGELOG markdown after all items are processed:
## Version {version} - Month Day, Year
### ✨ Features & Improvements:
- **CODAP-XXX:** Title here
- **CODAP-YYY:** Another title
### 🐞 Bug Fixes:
- **CODAP-AAA:** Fix description
- **CODAP-BBB:** Another fix
### 🛠️ Under the Hood:
- **CODAP-ZZZ:** Internal improvement
Rules:
- Order items by numeric Jira ID (223 before 1027)
- Only include sections that have items
- Use the release date from Phase 1
- Date format:
Month Day, Year (e.g., February 1, 2026)
Present generated markdown for approval:
Show the complete CHANGELOG entry, then ask:
- Approve - Release notes are ready, proceed to Phase 3
- Edit an item - Go back and change a specific item's section or title
- Reorder items - Change the order within sections
Note: Mention that Asset Sizes will be added in Phase 4 after the build.
Update Jira Fix Versions for all stories where user approved the update (during step 5).
IMPORTANT - Context Management: Jira MCP responses can be verbose and consume significant context. Delegate this bulk operation to a subagent:
Use the Task tool to update Fix Versions for all approved stories. Provide the subagent with:
- The list of CODAP-XXX story IDs to update
- The version string to set (e.g.,
3.0.0-beta.2664)
The subagent should report back ONLY:
- Success/failure count (e.g., "Updated 8/10 stories successfully")
- IDs of any stories that failed (e.g., "Failed: CODAP-123, CODAP-456")
Phase 3: Update Version Files
Goal: Sync translations, update all version-related files, and create release branch.
IMPORTANT — Working Directory Awareness:
- Scripts in
v3/scripts/ use cd v3 internally, which changes the shell's working directory for subsequent commands in the same Bash call.
- After running a v3 script, always verify your working directory with
pwd before running git commands.
- All
git commands must be run from the repository root (/path/to/codap), not from v3/.
- If a
git diff or git status command produces no output, do NOT assume "no changes" — verify by checking the working directory and trying again with correct paths. Empty output from git commands that should show changes is a red flag that something is wrong.
Steps
IMPORTANT — Branch policy: Never commit directly to main. The release
branch must be created before any commits (translations, version files, etc.).
Create release branch:
git checkout -b release-{version}
Branch naming rules:
- Pattern:
release-{version} where {version} is from Phase 1 (e.g., release-3.0.0-beta.2664)
- Do NOT use
/ in branch names
- Do NOT invent your own pattern
Sync translations with POEditor:
V3 owns all string pushes to POEditor — both DG.* and V3.* keys.
All English strings live in a single file: src/utilities/translation/lang/en-US.json5.
API Token: All scripts resolve the token in order: -a argument >
~/.porc > $POEDITOR_API_TOKEN env var. Only ask the user for a token
if none of these are configured.
2a. Preview English string changes before pushing:
Before pushing, pull the current English strings from POEditor and diff them
against the local en-US.json5 so the user can validate the changes.
cd v3
# Pull current English strings from POEditor to a temp file
./scripts/strings-pull.sh -p 125447 -l en-US -o /tmp
# Convert local JSON5 to JSON for comparison
node -e "
const fs = require('fs');
const JSON5 = require('json5');
const data = JSON5.parse(fs.readFileSync('src/utilities/translation/lang/en-US.json5', 'utf8'));
fs.writeFileSync('/tmp/en-US-local.json', JSON.stringify(data, null, 4) + '\n');
"
# Detailed diff showing new keys, changed values, and keys only in POEditor
node -e "
const poeditor = require('/tmp/en-US.json');
const local = require('/tmp/en-US-local.json');
const changed = [], newKeys = [], missingLocally = [];
for (const k of Object.keys(local)) {
if (!(k in poeditor)) newKeys.push(k);
else if (poeditor[k] !== local[k]) changed.push({key: k, old: poeditor[k], new: local[k]});
}
for (const k of Object.keys(poeditor)) {
if (!(k in local)) missingLocally.push(k);
}
console.log('=== VALUE CHANGES (' + changed.length + ' keys) ===');
changed.forEach(c => {
console.log(' ' + c.key);
console.log(' POEditor: ' + JSON.stringify(c.old));
console.log(' Local: ' + JSON.stringify(c.new));
console.log();
});
console.log('=== NEW KEYS (' + newKeys.length + ' keys) ===');
newKeys.forEach(k => console.log(' ' + k + ': ' + JSON.stringify(local[k])));
console.log();
console.log('=== KEYS IN POEDITOR BUT NOT LOCAL (' + missingLocally.length + ' keys) ===');
missingLocally.forEach(k => console.log(' ' + k + ': ' + JSON.stringify(poeditor[k])));
"
Show the diff to the user. Common expected changes:
- New keys added since the last release (lines only in local)
- Updated string values
Red flags to call out:
- Keys present in POEditor but missing locally (would NOT be deleted since
sync_terms=0, but worth noting)
- Unexpected value changes to existing keys
Ask the user to approve the push before proceeding. If the diff is empty
(no changes), note that and ask whether to skip the push.
2b. Push English strings to POEditor:
./scripts/strings-push-project.sh
This pushes all strings from en-US.json5 (both DG and V3 keys) to POEditor.
The push is additive (sync_terms=0) — it adds new terms and updates existing
values but never deletes terms. Push first so that the subsequent pull includes
any new keys added since the last release.
2c. Pull non-English translations:
./scripts/strings-pull-project.sh
This pulls translated strings for all supported languages. Report results to the
user (the streaming output may be collapsed in the UI).
2d. Verify and commit pulled translations:
Return to the repository root before running git commands:
cd /path/to/codap # repository root, NOT v3/
git status -- v3/src/utilities/translation/lang/
Report results to the user. If there are changes:
git add v3/src/utilities/translation/lang/
git commit -m "Update translations from POEditor"
Zero-width space handling: POEditor treats truly empty strings as
"untranslated," so the scripts convert between empty strings and zero-width
spaces (\u200b) at the boundary:
- Push (
strings-push.sh): "" → "\u200b" before uploading
- Pull (
strings-pull.sh): "\u200b" → "" after downloading
The source file (en-US.json5) and all runtime language files use "" for
intentionally blank strings — zero-width spaces should never appear in the
repository.
Update package.json version:
cd v3
npm version --no-git-tag-version {version}
IMPORTANT: Use the npm version command - do NOT manually edit package.json. The npm command updates both package.json AND package-lock.json.
Update versions.md:
Add new row at top of versions table (using release date from Phase 1):
| [{version}](https://codap3.concord.org/version/{version}/) | Month Day, Year |
Update CHANGELOG.md:
- Insert content from Phase 2 at top (after
# Changelog heading)
- Asset Sizes section added in Phase 4
Stage version files:
git add v3/package.json v3/package-lock.json v3/versions.md v3/CHANGELOG.md
Phase 4: Create Release PR
Goal: Build, capture asset sizes, commit, and create PR.
Steps
Run build:
cd v3 && npm run build
Get asset sizes:
ls -la v3/dist/assets
- Find
main.*.css file, get its size
- Find all
index.*.js files, use the largest one
- Strip hashes for display:
index.f6eac39a783c91ae9ea5.js → index.js
Calculate % change:
- Read previous sizes from top entry in CHANGELOG.md
- Calculate:
((new - old) / old) * 100
- Format:
X.XX%, <0.01% for very small increases, negative for decreases (e.g., -0.50%)
Add Asset Sizes to CHANGELOG:
### Asset Sizes
| File | Size | % Change from Previous Release |
|-----------|---------------|--------------------------------|
| main.css | XXXXXX bytes | X.XX% |
| index.js | XXXXXXX bytes | X.XX% |
Commit and push:
git add v3/CHANGELOG.md
git commit -m "Release {version}"
git push -u origin release-{version}
Note: Only commit the version files (package.json, package-lock.json, versions.md, CHANGELOG.md). Do not commit the dist/ build output.
Create PR with labels:
gh pr create \
--title "Release {version}" \
--body "{release_notes_from_phase_2}" \
--label "v3" \
--label "run regression"
Inform user:
PR created: {url}
CI is running. The run regression label triggers the full Cypress test suite.
After CI passes and PR is reviewed/merged, run /codap-v3-build tag to continue.
Phase 5: Tag and Release
Goal: After PR merge, create git tag and GitHub release.
Prerequisite: Release PR must be merged.
Steps
Checkout main and pull:
git checkout main
git pull
Create and push annotated tag:
git tag -a {version} -m "Version {version}"
git push origin {version}
Create GitHub release:
gh release create {version} \
--title "Version {version}" \
--notes "{release_notes_from_phase_2}"
Inform user and wait for S3 deploy:
Tag pushed and GitHub release created.
Watch GitHub Actions: https://github.com/concord-consortium/codap/actions
The tag push triggers a CI build that deploys to S3. Do not trigger the staging workflow until this deploy completes. Once the S3 deploy is done, the version will be available at:
https://codap3.concord.org/version/{version}/
Let me know when the deploy is complete and you're ready to proceed with staging, or run /codap-v3-build deploy {version} to continue.
IMPORTANT: Do NOT automatically trigger the staging workflow here. The staging workflow copies the build from S3, so it will fail if the tag's CI deploy hasn't finished yet. Wait for the user to confirm the deploy is complete.
Phase 6: Deploy
Goal: Stage, test, deploy to production and beta, finalize Jira.
Steps
Trigger staging workflow:
gh workflow run release-v3-staging.yml -f version={version}
Staging workflow triggered.
Watch: https://github.com/concord-consortium/codap/actions/workflows/release-v3-staging.yml
Test at: https://codap3.concord.org/index-staging.html
Post release announcement to Slack:
Post to the #codap-v3 channel in the Concord Consortium workspace (concord-consortium.slack.com).
If Slack MCP server is available:
- Ask user for permission to post
- Post the announcement using
mcp__slack__conversations_add_message
- Use
channel_id: #codap-v3 and content_type: text/markdown
If Slack MCP server is NOT available:
- Show the user a draft of the announcement
- Instruct them to paste it into Slack manually
Announcement format:
CODAP {version} is available for testing at https://codap3.concord.org/staging.
### ✨ Features & Improvements:
**CODAP-XXX:** Feature title here
**CODAP-YYY:** Another feature
### 🐞 Bug Fixes:
**CODAP-AAA:** Bug fix title
**CODAP-BBB:** Another fix
### 🛠️ Under the Hood:
**CODAP-ZZZ:** Internal change
The [beta](https://codap3.concord.org/beta) and [production](https://codap3.concord.org/) URLs will be updated once the staging build passes QA.
Rules:
- Use the version number from this release (e.g.,
3.0.0-beta.2664)
- Include only the sections that have items (Features, Bug Fixes, Under the Hood)
- Use the same titles and order as in CHANGELOG.md (including emoji prefixes in section headers)
- Each item on its own line with
**CODAP-XXX:** prefix
- End with the beta/production follow-up message (links should render in Slack)
Wait for external QA (may take 1+ days)
Let me know when staging QA is complete and we can proceed with production deployment.
If you'd prefer to complete deployment separately, see manual instructions below.
After QA approval, deploy to production and beta, then finalize Jira.
Manual Completion Instructions
If you prefer to complete deployment outside of Claude Code:
Deploy to production:
gh workflow run release-v3-production.yml -f version={version}
Or use GitHub UI: https://github.com/concord-consortium/codap/actions/workflows/release-v3-production.yml
Deploy to beta:
gh workflow run release-v3-beta.yml -f version={version}
Or use GitHub UI: https://github.com/concord-consortium/codap/actions/workflows/release-v3-beta.yml
Finalize Jira release:
- Go to CODAPv3 project in Jira
- Open "Manage Releases" tab
- Find release
{version}
- Mark as
Released
Resume Later
To complete deployment in Claude Code after QA:
/codap-v3-build deploy {version}
Staging QA Failure — Revised Release
Trigger: A show-stopper bug is found during Phase 6 staging QA, and a fix has been merged to main.
Invocation: /codap-v3-build fix {old-version} (e.g., /codap-v3-build fix 3.0.0-beta.2803)
When invoked, introduce the situation:
A bug was found during staging QA for {old-version} and a fix has been merged.
This workflow will create a revised release with an updated version number.
I'll walk you through:
- Determine the new version number and release date
- Decide whether release notes need updating
- Update version files
- Build and create a new release PR
- Clean up the old tag/release and create new ones
- Update Jira and re-deploy to staging
Step 1: Gather Context
Ensure on main with latest:
git checkout main
git pull
Get current build number and verify the fix is present:
cat v3/build_number.json
git log --oneline {old-version}..HEAD
Confirm with the user that the expected fix commit(s) appear in the log.
Determine new version number:
- Current build number is N (from
build_number.json)
- The release PR will increment it once more when merged → version is N + 1
- Example: If build number is
2804, new version is 3.0.0-beta.2805
- Match the version pattern of
{old-version} (same prefix, new build number)
Confirm release date:
- The original release date (from Phase 1) may no longer be appropriate if QA and the fix took multiple days.
- Show the original release date and today's date.
- Ask the user to confirm or update the release date.
- This date will be used in CHANGELOG.md, versions.md, and the Jira release.
Confirm with user:
The fix is on main. New version will be {new-version} (old was {old-version}).
Release date: {release-date}
Does this look correct?
Step 2: Release Notes Decision
Ask the user:
Do the release notes need to be updated?
- No changes needed — The bug was introduced in this release cycle, so users never saw it
- Add the fix — The bug existed in a prior release and the fix should be documented
If no changes needed:
- The existing CHANGELOG content will be reused with only the version number and date updated in the header.
If release notes need updating:
- Walk through the new fix item(s) using the same interactive process as Phase 2, step 5 (present title options, ask for section and title).
- Insert the new item(s) into the appropriate section(s) of the existing release notes, maintaining numeric Jira ID order.
- Present the updated CHANGELOG entry for approval.
- Update Jira Fix Versions for any newly added stories.
Step 3: Create Release Branch and Update Files
Follow the same working directory rules as Phase 3.
Create release branch:
git checkout -b release-{new-version}
Sync translations (only if needed):
- Only perform the translation sync (Phase 3, step 2) if the bug fix introduced new or changed translatable strings.
- For most bug fixes, this can be skipped. Ask the user if unsure.
Update package.json:
cd v3
npm version --no-git-tag-version {new-version}
Update versions.md:
- Replace the
{old-version} row with the {new-version} row (using the confirmed release date)
- Do NOT add a second row — this is a revision, not a separate release
Update CHANGELOG.md:
- Replace the
## Version {old-version} header with ## Version {new-version}, using the confirmed release date
- If release notes content changed (Step 2), update the content as well
- The Asset Sizes section will be updated after the build (Step 4)
Commit version file changes:
cd /path/to/codap
git add v3/package.json v3/package-lock.json v3/versions.md v3/CHANGELOG.md
git commit -m "Release {new-version}"
Step 4: Build, Asset Sizes, and Release PR
Follow the same process as Phase 4:
Build:
cd v3 && npm run build
Update asset sizes in CHANGELOG.md (same process as Phase 4, steps 2–4).
- Compare against the previous release before {old-version} for % change (since
{old-version} is being replaced, not used as baseline).
Commit, push, and create PR:
cd /path/to/codap
git add v3/CHANGELOG.md
git commit --amend --no-edit
git push -u origin release-{new-version}
gh pr create \
--title "Release {new-version}" \
--body "{release_notes}" \
--label "v3" \
--label "run regression"
Inform user:
PR created: {url}
After CI passes and PR is merged, I'll clean up the old release and create the new one.
Step 5: After PR Merge — Clean Up and Re-tag
Prerequisite: Release PR must be merged.
Checkout main and pull:
git checkout main
git pull
Delete old GitHub release and tag:
gh release delete {old-version} --yes
git push origin --delete {old-version}
git tag -d {old-version}
These are safe to delete because:
- The release was never deployed to production or beta
- The tag points to a known-buggy build
- No external consumers depend on it
Create new tag and GitHub release:
git tag -a {new-version} -m "Version {new-version}"
git push origin {new-version}
gh release create {new-version} \
--title "Version {new-version}" \
--notes "{release_notes}"
Delete old release branch (optional cleanup):
git push origin --delete release-{old-version}
git branch -d release-{old-version}
Inform user and wait for S3 deploy:
Old release cleaned up. New tag and GitHub release created.
Watch GitHub Actions: https://github.com/concord-consortium/codap/actions
The tag push triggers a CI build that deploys to S3. Do not trigger the staging workflow until this deploy completes. Once the S3 deploy is done, the version will be available at:
https://codap3.concord.org/version/{new-version}/
Let me know when the deploy is complete and we can proceed with Jira updates and staging.
IMPORTANT: Do NOT automatically trigger the staging workflow here. The staging workflow copies the build from S3, so it will fail if the tag's CI deploy hasn't finished yet. Wait for the user to confirm the deploy is complete.
Step 6: Update Jira and Re-deploy
Update Jira release version:
- Rename the Jira release from
{old-version} to {new-version}
- Update the release date if it changed
- If new stories were added to release notes (Step 2), update their Fix Versions
Context management: Delegate Jira updates to a subagent (same pattern as Phase 2, step 9).
Re-deploy to staging:
gh workflow run release-v3-staging.yml -f version={new-version}
Post updated Slack announcement (same format as Phase 6, step 2, but note it's a revised build):
CODAP {new-version} is available for testing at https://codap3.concord.org/staging.
(Revised build — replaces {old-version} which had a staging QA issue.)
{same release notes sections as before}
The [beta](https://codap3.concord.org/beta) and [production](https://codap3.concord.org/) URLs will be updated once the staging build passes QA.
Inform user:
Revised release {new-version} deployed to staging.
Test at: https://codap3.concord.org/index-staging.html
When staging QA passes, run /codap-v3-build deploy {new-version} to continue with production deployment.
File Locations
| File |
Purpose |
v3/build_number.json |
Current build number |
v3/package.json |
Version field |
v3/versions.md |
Version history table |
v3/CHANGELOG.md |
Release notes |
v3/dist/assets/ |
Built assets (after npm run build) |
v3/src/utilities/translation/lang/en-US.json5 |
All English strings (DG + V3, JSON5, source of truth) |
Jira Integration
Use these constants for all Atlassian MCP tool calls:
| Constant |
Value |
cloudId |
concord-consortium.atlassian.net |
projectKey |
CODAP |
Note: The Atlassian MCP tools accept either a UUID cloud ID or a site URL for the cloudId parameter. The site URL format is used here for readability.
- Use Atlassian MCP tools for all Jira operations
- Stories tagged via
Fix versions field
- Release marked
Released after production deploy
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: codap-v3-build3description: Use when preparing a CODAP v3 release, creating release notes, updating version files, creating release PRs, tagging releases, or deploying to staging/production. Invoke with phase name or version number to resume.4---56# CODAP v3 Build & Release78## Overview910Interactive workflow for CODAP v3 releases. Guides you through Jira setup, release notes generation, version updates, PR creation, tagging, and deployment.1112## Quick Reference1314| Phase | Command | Description |15|-------|---------|-------------|16| 1 | `/codap-v3-build` | Prepare release (Jira version, tag stories) |17| 2 | `/codap-v3-build notes` | Prepare release notes (interactive) |18| 3 | `/codap-v3-build files` | Update version files |19| 4 | `/codap-v3-build pr` | Create release PR |20| 5 | `/codap-v3-build tag` | Tag and create GitHub release |21| 6 | `/codap-v3-build deploy [version]` | Deploy to staging/production |22| fix | `/codap-v3-build fix {old-version}` | Revise release after staging QA failure |2324## Getting Started2526When invoked, introduce the skill:2728> This skill will walk you through the process of building a release of CODAP v3. The process has 6 phases:29>30> 1. **Prepare the Release** - Set up Jira version and gather context31> 2. **Prepare Release Notes** - Interactive walkthrough to create CHANGELOG entry32> 3. **Update Version Files** - Update package.json, versions.md, CHANGELOG.md33> 4. **Create Release PR** - Build, capture asset sizes, create PR34> 5. **Tag and Release** - After PR merge, create git tag and GitHub release35> 6. **Deploy** - Stage, QA, deploy to production36>37> Are you ready to proceed?3839Wait for user confirmation before starting Phase 1.4041## Phase 1: Prepare the Release4243**Goal:** Create Jira release version and gather context.4445### Steps46471. **Check workspace status:**48 ```bash49 git status50 ```5152 - If there are modified/staged files, ask user to: **Stash** / **Commit** / **Discard**53 - Untracked files are okay to leave54552. **Ensure on main branch with latest:**56 ```bash57 git checkout main58 git pull59 ```60613. **Get current build number:**62 ```bash63 cat v3/build_number.json64 ```65664. **Get previous release tag:**67 ```bash68 git tag --sort=-creatordate | head -569 ```70715. **Show context to user:**72 - Display last 3 releases (version and date)73 - Show current build number from `build_number.json`74756. **Determine recommended version string:**76 - Use build number **+ 1** (build number auto-increments when release PR merges)77 - Match previous release pattern (e.g., `-beta`, `-pre`)78 - Example: If build number is `2662`, recommend `3.0.0-beta.2663`79807. **Get previous release date from Jira** (for start date default)81828. **Ask user for Jira release details:**8384 | Field | Default | Options |85 |-------|---------|---------|86 | Version name | Based on previous release pattern + new build number | Match previous pattern (e.g., `-beta`, `-rc`, or release) |87 | Start date | Previous release date | User can modify |88 | Release date | Today's date | Today / Tomorrow / Custom future date |89 | Description | `Version {version}` | User can modify |9091 **Note:** The release date chosen here is used throughout the process:92 - CHANGELOG.md header date93 - versions.md entry date94 - Jira release date95969. **Create Jira release version** using Atlassian MCP tools (status: `Unreleased`)9798## Phase 2: Prepare Release Notes99100**Goal:** Generate CHANGELOG entry with user-selected titles.101102### Steps1031041. **Get PRs since last release:**105 ```bash106 git log <last-tag>..HEAD --oneline | grep -E '\(#[0-9]+\)|Merge pull request #[0-9]+'107 ```108109 **Note:** This finds both regular merge commits AND squash-merged PRs (which include `(#123)` in the commit message). Using `--merges` alone misses squash merges.1101112. **Get PR details from GitHub:**112 ```bash113 gh pr view <number> --json number,title,headRefName114 ```1151163. **Match PRs to Jira stories by CODAP-XXX ID:**117 - Check branch name first (most reliable, e.g., `CODAP-1027-inbounds-url-param`)118 - Then PR title (e.g., `CODAP-1027: Implement inbounds parameter`)119 - Use caution with PR descriptions - they may reference related stories (e.g., "Follow-up to CODAP-XXX") that aren't the primary story for this PR120 - Extract unique CODAP-XXX IDs1211224. **For each matched item, fetch:**123 - Jira story details (summary, issue type, **current status**)124 - PR title from GitHub125 - Generate AI-suggested title (concise, user-focused)1261275. **Interactive walkthrough for each item:**128129 **IMPORTANT - NO SHORTCUTS:**130 - Do NOT ask user to approve the entire list at once131 - Do NOT batch items together (e.g., "approve these 3 items")132 - Do NOT skip showing title options133 - ALWAYS go through items ONE BY ONE, presenting all title options for each134135 **IMPORTANT - PRESENTATION ORDER:**136 Present the title options table as markdown output FIRST, then use AskUserQuestion. This prevents the question UI from covering the options.137138 First, output this markdown:139 ```140 ### Item 1/8: CODAP-1027 (Story)141142 | Source | Title |143 |--------|-------|144 | **AI suggestion** | {ai_title} |145 | **Jira** | {jira_summary} |146 | **PR** | {pr_title} |147 ```148149 **Note:** Strip Jira IDs from PR titles before presenting (e.g., "CODAP-138: Fix point color" → "Fix point color")150151 **Jira status notice (if not Done):**152 If the story's Jira status is anything other than "Done" (e.g., "In Project Team Review", "In Code Review"), append the status to the item header line with a warning indicator:153 ```154 ### Item 1/8: CODAP-1027 (Story) — Jira status: In Project Team Review ⚠️155 ```156 Do NOT add the status suffix for stories that are "Done". The user can choose to exclude the story via the Section question.157158 Then ask questions using AskUserQuestion (Section and Title are TWO SEPARATE CALLS so Title is skipped if Exclude):159160 **Section question:**161 - Question: "Which section for this item?"162 - Options: Features / Bug Fixes / Under the Hood / Exclude163 - Add "(Recommended)" to Features for Stories, Bug Fixes for Bugs164 - If user types in "Other", interpret as an instruction (e.g., "go back to previous item") and handle accordingly165166 **If Section is NOT Exclude - ask Title question:**167 - Question: "Which title? (See table above, or type your preferred title in 'Other')"168 - Options: AI suggestion / Jira / PR (no "Custom" - user types preferred title in built-in "Other")169 - If user types in "Other", use their text as the title170 - **Title option order must ALWAYS be:** AI suggestion, Jira, PR (both in table and in question options)171 - Stories included in release notes will have their Fix Version updated automatically (tracked for step 9)172173 **If Section IS Exclude - ask Fix Version question:**174 - Question: "Should this story's Fix Version be set to this release?"175 - Options: Yes / No176 - Default recommendation: **Yes (Recommended)** - infrastructure improvements may not be user-facing but should still be tracked in Jira177 - If **Yes**: Add to Fix Version update list (step 9) even though excluded from release notes178 - If **No**: Do not update Fix Version (e.g., if the story was fixed in a prior release, or the PR isn't part of this release)179180 After selection, confirm:181 > ✓ **CODAP-1027** → Features: "Selected title here"1821836. **For PRs without Jira IDs:**184 - Show PR title only185 - Default recommendation: **Exclude (Recommended)** for docs, dependencies, maintenance186 - Option to include in Under the Hood if relevant187 - No Fix Version to update (no Jira story)1881897. **Generate CHANGELOG markdown** after all items are processed:190191 ```markdown192 ## Version {version} - Month Day, Year193194 ### ✨ Features & Improvements:195 - **CODAP-XXX:** Title here196 - **CODAP-YYY:** Another title197198 ### 🐞 Bug Fixes:199 - **CODAP-AAA:** Fix description200 - **CODAP-BBB:** Another fix201202 ### 🛠️ Under the Hood:203 - **CODAP-ZZZ:** Internal improvement204 ```205206 **Rules:**207 - Order items by **numeric** Jira ID (223 before 1027)208 - Only include sections that have items209 - Use the release date from Phase 1210 - Date format: `Month Day, Year` (e.g., `February 1, 2026`)2112128. **Present generated markdown for approval:**213214 Show the complete CHANGELOG entry, then ask:215 - **Approve** - Release notes are ready, proceed to Phase 3216 - **Edit an item** - Go back and change a specific item's section or title217 - **Reorder items** - Change the order within sections218219 Note: Mention that Asset Sizes will be added in Phase 4 after the build.2202219. **Update Jira Fix Versions** for all stories where user approved the update (during step 5).222223 **IMPORTANT - Context Management:** Jira MCP responses can be verbose and consume significant context. Delegate this bulk operation to a subagent:224225 > Use the Task tool to update Fix Versions for all approved stories. Provide the subagent with:226 > - The list of CODAP-XXX story IDs to update227 > - The version string to set (e.g., `3.0.0-beta.2664`)228 >229 > The subagent should report back ONLY:230 > - Success/failure count (e.g., "Updated 8/10 stories successfully")231 > - IDs of any stories that failed (e.g., "Failed: CODAP-123, CODAP-456")232233## Phase 3: Update Version Files234235**Goal:** Sync translations, update all version-related files, and create release branch.236237**IMPORTANT — Working Directory Awareness:**238- Scripts in `v3/scripts/` use `cd v3` internally, which changes the shell's working directory for subsequent commands in the same Bash call.239- After running a v3 script, always verify your working directory with `pwd` before running git commands.240- **All `git` commands must be run from the repository root** (`/path/to/codap`), not from `v3/`.241- If a `git diff` or `git status` command produces **no output**, do NOT assume "no changes" — verify by checking the working directory and trying again with correct paths. Empty output from git commands that should show changes is a red flag that something is wrong.242243### Steps244245**IMPORTANT — Branch policy:** Never commit directly to `main`. The release246branch must be created before any commits (translations, version files, etc.).2472481. **Create release branch:**249 ```bash250 git checkout -b release-{version}251 ```252253 **Branch naming rules:**254 - Pattern: `release-{version}` where `{version}` is from Phase 1 (e.g., `release-3.0.0-beta.2664`)255 - Do NOT use `/` in branch names256 - Do NOT invent your own pattern2572582. **Sync translations with POEditor:**259260 V3 owns all string pushes to POEditor — both `DG.*` and `V3.*` keys.261 All English strings live in a single file: `src/utilities/translation/lang/en-US.json5`.262263 **API Token:** All scripts resolve the token in order: `-a` argument >264 `~/.porc` > `$POEDITOR_API_TOKEN` env var. Only ask the user for a token265 if none of these are configured.266267 **2a. Preview English string changes before pushing:**268269 Before pushing, pull the current English strings from POEditor and diff them270 against the local `en-US.json5` so the user can validate the changes.271272 ```bash273 cd v3274 # Pull current English strings from POEditor to a temp file275 ./scripts/strings-pull.sh -p 125447 -l en-US -o /tmp276 # Convert local JSON5 to JSON for comparison277 node -e "278 const fs = require('fs');279 const JSON5 = require('json5');280 const data = JSON5.parse(fs.readFileSync('src/utilities/translation/lang/en-US.json5', 'utf8'));281 fs.writeFileSync('/tmp/en-US-local.json', JSON.stringify(data, null, 4) + '\n');282 "283 # Detailed diff showing new keys, changed values, and keys only in POEditor284 node -e "285 const poeditor = require('/tmp/en-US.json');286 const local = require('/tmp/en-US-local.json');287 const changed = [], newKeys = [], missingLocally = [];288 for (const k of Object.keys(local)) {289 if (!(k in poeditor)) newKeys.push(k);290 else if (poeditor[k] !== local[k]) changed.push({key: k, old: poeditor[k], new: local[k]});291 }292 for (const k of Object.keys(poeditor)) {293 if (!(k in local)) missingLocally.push(k);294 }295 console.log('=== VALUE CHANGES (' + changed.length + ' keys) ===');296 changed.forEach(c => {297 console.log(' ' + c.key);298 console.log(' POEditor: ' + JSON.stringify(c.old));299 console.log(' Local: ' + JSON.stringify(c.new));300 console.log();301 });302 console.log('=== NEW KEYS (' + newKeys.length + ' keys) ===');303 newKeys.forEach(k => console.log(' ' + k + ': ' + JSON.stringify(local[k])));304 console.log();305 console.log('=== KEYS IN POEDITOR BUT NOT LOCAL (' + missingLocally.length + ' keys) ===');306 missingLocally.forEach(k => console.log(' ' + k + ': ' + JSON.stringify(poeditor[k])));307 "308 ```309310 Show the diff to the user. Common expected changes:311 - New keys added since the last release (lines only in local)312 - Updated string values313314 **Red flags to call out:**315 - Keys present in POEditor but missing locally (would NOT be deleted since316 `sync_terms=0`, but worth noting)317 - Unexpected value changes to existing keys318319 Ask the user to approve the push before proceeding. If the diff is empty320 (no changes), note that and ask whether to skip the push.321322 **2b. Push English strings to POEditor:**323 ```bash324 ./scripts/strings-push-project.sh325 ```326 This pushes all strings from `en-US.json5` (both DG and V3 keys) to POEditor.327 The push is additive (`sync_terms=0`) — it adds new terms and updates existing328 values but never deletes terms. Push first so that the subsequent pull includes329 any new keys added since the last release.330331 **2c. Pull non-English translations:**332 ```bash333 ./scripts/strings-pull-project.sh334 ```335 This pulls translated strings for all supported languages. Report results to the336 user (the streaming output may be collapsed in the UI).337338 **2d. Verify and commit pulled translations:**339340 Return to the repository root before running git commands:341 ```bash342 cd /path/to/codap # repository root, NOT v3/343 git status -- v3/src/utilities/translation/lang/344 ```345 Report results to the user. If there are changes:346 ```bash347 git add v3/src/utilities/translation/lang/348 git commit -m "Update translations from POEditor"349 ```350351 **Zero-width space handling:** POEditor treats truly empty strings as352 "untranslated," so the scripts convert between empty strings and zero-width353 spaces (`\u200b`) at the boundary:354 - **Push** (`strings-push.sh`): `""` → `"\u200b"` before uploading355 - **Pull** (`strings-pull.sh`): `"\u200b"` → `""` after downloading356357 The source file (`en-US.json5`) and all runtime language files use `""` for358 intentionally blank strings — zero-width spaces should never appear in the359 repository.3603613. **Update package.json version:**362 ```bash363 cd v3364 npm version --no-git-tag-version {version}365 ```366367 **IMPORTANT:** Use the `npm version` command - do NOT manually edit package.json. The npm command updates both package.json AND package-lock.json.3683694. **Update versions.md:**370371 Add new row at top of versions table (using release date from Phase 1):372 ```markdown373 | [{version}](https://codap3.concord.org/version/{version}/) | Month Day, Year |374 ```3753765. **Update CHANGELOG.md:**377 - Insert content from Phase 2 at top (after `# Changelog` heading)378 - Asset Sizes section added in Phase 43793806. **Stage version files:**381 ```bash382 git add v3/package.json v3/package-lock.json v3/versions.md v3/CHANGELOG.md383 ```384385## Phase 4: Create Release PR386387**Goal:** Build, capture asset sizes, commit, and create PR.388389### Steps3903911. **Run build:**392 ```bash393 cd v3 && npm run build394 ```3953962. **Get asset sizes:**397 ```bash398 ls -la v3/dist/assets399 ```400401 - Find `main.*.css` file, get its size402 - Find all `index.*.js` files, use the **largest** one403 - Strip hashes for display: `index.f6eac39a783c91ae9ea5.js` → `index.js`4044053. **Calculate % change:**406 - Read previous sizes from top entry in CHANGELOG.md407 - Calculate: `((new - old) / old) * 100`408 - Format: `X.XX%`, `<0.01%` for very small increases, negative for decreases (e.g., `-0.50%`)4094104. **Add Asset Sizes to CHANGELOG:**411 ```markdown412 ### Asset Sizes413 | File | Size | % Change from Previous Release |414 |-----------|---------------|--------------------------------|415 | main.css | XXXXXX bytes | X.XX% |416 | index.js | XXXXXXX bytes | X.XX% |417 ```4184195. **Commit and push:**420 ```bash421 git add v3/CHANGELOG.md422 git commit -m "Release {version}"423 git push -u origin release-{version}424 ```425426 **Note:** Only commit the version files (package.json, package-lock.json, versions.md, CHANGELOG.md). Do not commit the `dist/` build output.4274286. **Create PR with labels:**429 ```bash430 gh pr create \431 --title "Release {version}" \432 --body "{release_notes_from_phase_2}" \433 --label "v3" \434 --label "run regression"435 ```4364377. **Inform user:**438 > **PR created:** {url}439 >440 > CI is running. The `run regression` label triggers the full Cypress test suite.441 >442 > After CI passes and PR is reviewed/merged, run `/codap-v3-build tag` to continue.443444## Phase 5: Tag and Release445446**Goal:** After PR merge, create git tag and GitHub release.447448**Prerequisite:** Release PR must be merged.449450### Steps4514521. **Checkout main and pull:**453 ```bash454 git checkout main455 git pull456 ```4574582. **Create and push annotated tag:**459 ```bash460 git tag -a {version} -m "Version {version}"461 git push origin {version}462 ```4634643. **Create GitHub release:**465 ```bash466 gh release create {version} \467 --title "Version {version}" \468 --notes "{release_notes_from_phase_2}"469 ```4704714. **Inform user and wait for S3 deploy:**472 > **Tag pushed and GitHub release created.**473 >474 > Watch GitHub Actions: https://github.com/concord-consortium/codap/actions475 >476 > The tag push triggers a CI build that deploys to S3. **Do not trigger the staging workflow until this deploy completes.** Once the S3 deploy is done, the version will be available at:477 > https://codap3.concord.org/version/{version}/478 >479 > Let me know when the deploy is complete and you're ready to proceed with staging, or run `/codap-v3-build deploy {version}` to continue.480481 **IMPORTANT:** Do NOT automatically trigger the staging workflow here. The staging workflow copies the build from S3, so it will fail if the tag's CI deploy hasn't finished yet. Wait for the user to confirm the deploy is complete.482483## Phase 6: Deploy484485**Goal:** Stage, test, deploy to production and beta, finalize Jira.486487### Steps4884891. **Trigger staging workflow:**490 ```bash491 gh workflow run release-v3-staging.yml -f version={version}492 ```493494 > **Staging workflow triggered.**495 >496 > Watch: https://github.com/concord-consortium/codap/actions/workflows/release-v3-staging.yml497 >498 > Test at: https://codap3.concord.org/index-staging.html4995002. **Post release announcement to Slack:**501502 Post to the `#codap-v3` channel in the Concord Consortium workspace (`concord-consortium.slack.com`).503504 **If Slack MCP server is available:**505 - Ask user for permission to post506 - Post the announcement using `mcp__slack__conversations_add_message`507 - Use `channel_id: #codap-v3` and `content_type: text/markdown`508509 **If Slack MCP server is NOT available:**510 - Show the user a draft of the announcement511 - Instruct them to paste it into Slack manually512513 **Announcement format:**514515 ```markdown516 CODAP {version} is available for testing at https://codap3.concord.org/staging.517518 ### ✨ Features & Improvements:519 **CODAP-XXX:** Feature title here520 **CODAP-YYY:** Another feature521522 ### 🐞 Bug Fixes:523 **CODAP-AAA:** Bug fix title524 **CODAP-BBB:** Another fix525526 ### 🛠️ Under the Hood:527 **CODAP-ZZZ:** Internal change528529 The [beta](https://codap3.concord.org/beta) and [production](https://codap3.concord.org/) URLs will be updated once the staging build passes QA.530 ```531532 **Rules:**533 - Use the version number from this release (e.g., `3.0.0-beta.2664`)534 - Include only the sections that have items (Features, Bug Fixes, Under the Hood)535 - Use the same titles and order as in CHANGELOG.md (including emoji prefixes in section headers)536 - Each item on its own line with `**CODAP-XXX:**` prefix537 - End with the beta/production follow-up message (links should render in Slack)5385393. **Wait for external QA** (may take 1+ days)540541 > **Let me know when staging QA is complete** and we can proceed with production deployment.542 >543 > If you'd prefer to complete deployment separately, see manual instructions below.5445454. **After QA approval, deploy to production and beta, then finalize Jira.**546547### Manual Completion Instructions548549If you prefer to complete deployment outside of Claude Code:550551**Deploy to production:**552```bash553gh workflow run release-v3-production.yml -f version={version}554```555Or use GitHub UI: https://github.com/concord-consortium/codap/actions/workflows/release-v3-production.yml556557**Deploy to beta:**558```bash559gh workflow run release-v3-beta.yml -f version={version}560```561Or use GitHub UI: https://github.com/concord-consortium/codap/actions/workflows/release-v3-beta.yml562563**Finalize Jira release:**5641. Go to CODAPv3 project in Jira5652. Open "Manage Releases" tab5663. Find release `{version}`5674. Mark as `Released`568569### Resume Later570571To complete deployment in Claude Code after QA:572```573/codap-v3-build deploy {version}574```575576## Staging QA Failure — Revised Release577578**Trigger:** A show-stopper bug is found during Phase 6 staging QA, and a fix has been merged to `main`.579580**Invocation:** `/codap-v3-build fix {old-version}` (e.g., `/codap-v3-build fix 3.0.0-beta.2803`)581582When invoked, introduce the situation:583584> A bug was found during staging QA for **{old-version}** and a fix has been merged.585> This workflow will create a revised release with an updated version number.586>587> I'll walk you through:588> 1. Determine the new version number and release date589> 2. Decide whether release notes need updating590> 3. Update version files591> 4. Build and create a new release PR592> 5. Clean up the old tag/release and create new ones593> 6. Update Jira and re-deploy to staging594595### Step 1: Gather Context5965971. **Ensure on main with latest:**598 ```bash599 git checkout main600 git pull601 ```6026032. **Get current build number and verify the fix is present:**604 ```bash605 cat v3/build_number.json606 git log --oneline {old-version}..HEAD607 ```608609 Confirm with the user that the expected fix commit(s) appear in the log.6106113. **Determine new version number:**612 - Current build number is N (from `build_number.json`)613 - The release PR will increment it once more when merged → version is **N + 1**614 - Example: If build number is `2804`, new version is `3.0.0-beta.2805`615 - Match the version pattern of `{old-version}` (same prefix, new build number)6166174. **Confirm release date:**618 - The original release date (from Phase 1) may no longer be appropriate if QA and the fix took multiple days.619 - Show the original release date and today's date.620 - Ask the user to confirm or update the release date.621 - This date will be used in CHANGELOG.md, versions.md, and the Jira release.6226235. **Confirm with user:**624 > The fix is on main. New version will be **{new-version}** (old was {old-version}).625 > Release date: **{release-date}**626 >627 > Does this look correct?628629### Step 2: Release Notes Decision630631Ask the user:632633> Do the release notes need to be updated?634>635> - **No changes needed** — The bug was introduced in this release cycle, so users never saw it636> - **Add the fix** — The bug existed in a prior release and the fix should be documented637638**If no changes needed:**639- The existing CHANGELOG content will be reused with only the version number and date updated in the header.640641**If release notes need updating:**642- Walk through the new fix item(s) using the same interactive process as Phase 2, step 5 (present title options, ask for section and title).643- Insert the new item(s) into the appropriate section(s) of the existing release notes, maintaining numeric Jira ID order.644- Present the updated CHANGELOG entry for approval.645- Update Jira Fix Versions for any newly added stories.646647### Step 3: Create Release Branch and Update Files648649Follow the same working directory rules as Phase 3.6506511. **Create release branch:**652 ```bash653 git checkout -b release-{new-version}654 ```6556562. **Sync translations (only if needed):**657 - Only perform the translation sync (Phase 3, step 2) if the bug fix introduced new or changed translatable strings.658 - For most bug fixes, this can be skipped. Ask the user if unsure.6596603. **Update package.json:**661 ```bash662 cd v3663 npm version --no-git-tag-version {new-version}664 ```6656664. **Update versions.md:**667 - **Replace** the `{old-version}` row with the `{new-version}` row (using the confirmed release date)668 - Do NOT add a second row — this is a revision, not a separate release6696705. **Update CHANGELOG.md:**671 - **Replace** the `## Version {old-version}` header with `## Version {new-version}`, using the confirmed release date672 - If release notes content changed (Step 2), update the content as well673 - The Asset Sizes section will be updated after the build (Step 4)6746756. **Commit version file changes:**676 ```bash677 cd /path/to/codap678 git add v3/package.json v3/package-lock.json v3/versions.md v3/CHANGELOG.md679 git commit -m "Release {new-version}"680 ```681682### Step 4: Build, Asset Sizes, and Release PR683684Follow the same process as Phase 4:6856861. **Build:**687 ```bash688 cd v3 && npm run build689 ```6906912. **Update asset sizes** in CHANGELOG.md (same process as Phase 4, steps 2–4).692 - Compare against the **previous release before {old-version}** for % change (since `{old-version}` is being replaced, not used as baseline).6936943. **Commit, push, and create PR:**695 ```bash696 cd /path/to/codap697 git add v3/CHANGELOG.md698 git commit --amend --no-edit699 git push -u origin release-{new-version}700 gh pr create \701 --title "Release {new-version}" \702 --body "{release_notes}" \703 --label "v3" \704 --label "run regression"705 ```7067074. **Inform user:**708 > **PR created:** {url}709 >710 > After CI passes and PR is merged, I'll clean up the old release and create the new one.711712### Step 5: After PR Merge — Clean Up and Re-tag713714**Prerequisite:** Release PR must be merged.7157161. **Checkout main and pull:**717 ```bash718 git checkout main719 git pull720 ```7217222. **Delete old GitHub release and tag:**723 ```bash724 gh release delete {old-version} --yes725 git push origin --delete {old-version}726 git tag -d {old-version}727 ```728729 These are safe to delete because:730 - The release was never deployed to production or beta731 - The tag points to a known-buggy build732 - No external consumers depend on it7337343. **Create new tag and GitHub release:**735 ```bash736 git tag -a {new-version} -m "Version {new-version}"737 git push origin {new-version}738 gh release create {new-version} \739 --title "Version {new-version}" \740 --notes "{release_notes}"741 ```7427434. **Delete old release branch** (optional cleanup):744 ```bash745 git push origin --delete release-{old-version}746 git branch -d release-{old-version}747 ```7487495. **Inform user and wait for S3 deploy:**750 > **Old release cleaned up. New tag and GitHub release created.**751 >752 > Watch GitHub Actions: https://github.com/concord-consortium/codap/actions753 >754 > The tag push triggers a CI build that deploys to S3. **Do not trigger the staging workflow until this deploy completes.** Once the S3 deploy is done, the version will be available at:755 > https://codap3.concord.org/version/{new-version}/756 >757 > Let me know when the deploy is complete and we can proceed with Jira updates and staging.758759 **IMPORTANT:** Do NOT automatically trigger the staging workflow here. The staging workflow copies the build from S3, so it will fail if the tag's CI deploy hasn't finished yet. Wait for the user to confirm the deploy is complete.760761### Step 6: Update Jira and Re-deploy7627631. **Update Jira release version:**764 - Rename the Jira release from `{old-version}` to `{new-version}`765 - Update the release date if it changed766 - If new stories were added to release notes (Step 2), update their Fix Versions767768 **Context management:** Delegate Jira updates to a subagent (same pattern as Phase 2, step 9).7697702. **Re-deploy to staging:**771 ```bash772 gh workflow run release-v3-staging.yml -f version={new-version}773 ```7747753. **Post updated Slack announcement** (same format as Phase 6, step 2, but note it's a revised build):776777 ```markdown778 CODAP {new-version} is available for testing at https://codap3.concord.org/staging.779 (Revised build — replaces {old-version} which had a staging QA issue.)780781 {same release notes sections as before}782783 The [beta](https://codap3.concord.org/beta) and [production](https://codap3.concord.org/) URLs will be updated once the staging build passes QA.784 ```7857864. **Inform user:**787 > **Revised release {new-version} deployed to staging.**788 >789 > Test at: https://codap3.concord.org/index-staging.html790 >791 > When staging QA passes, run `/codap-v3-build deploy {new-version}` to continue with production deployment.792793## File Locations794795| File | Purpose |796|------|---------|797| `v3/build_number.json` | Current build number |798| `v3/package.json` | Version field |799| `v3/versions.md` | Version history table |800| `v3/CHANGELOG.md` | Release notes |801| `v3/dist/assets/` | Built assets (after `npm run build`) |802| `v3/src/utilities/translation/lang/en-US.json5` | All English strings (DG + V3, JSON5, source of truth) |803804## Jira Integration805806Use these constants for all Atlassian MCP tool calls:807808| Constant | Value |809|----------|-------|810| `cloudId` | `concord-consortium.atlassian.net` |811| `projectKey` | `CODAP` |812813> **Note:** The Atlassian MCP tools accept either a UUID cloud ID or a site URL for the `cloudId` parameter. The site URL format is used here for readability.814815- Use Atlassian MCP tools for all Jira operations816- Stories tagged via `Fix versions` field817- Release marked `Released` after production deploy818819---820> Converted and distributed by [TomeVault](https://tomevault.io/claim/concord-consortium) — claim your Tome and manage your conversions.821<!-- tomevault:4.0:skill_md:2026-04-11 -->