Skip to content

Commit 5ffc938

Browse files
feat: changelog-first release flow with build artifacts on draft releases (#125)
* docs: add administrative guide for CI/CD infrastructure Documents GitHub Workflows, protected environments, secrets, variables, permissions, security posture, code ownership, and release process for administrators and AI coding agents. * feat: add build artifacts to draft releases - release.yml: create draft release instead of published (draft: true) - codebuild.yml: add v* tag trigger, elevate contents permission to write, add resilient release upload step that handles all states (draft exists, no release, already published with graceful failure) - docs: update ADMINISTRATIVE_GUIDE.md with new release flow The release process now requires human review before publishing: tag push → draft release created → CodeBuild artifacts attached → human reviews and publishes → changelog workflow triggered. * docs: replace ASCII pipeline diagrams with mermaid in admin guide Adds manual approval gate (codebuild environment) to the diagrams and shows the resilient three-state release upload logic visually. * feat: add workflow_dispatch tag input and mermaid diagrams - codebuild.yml: add optional tag input to workflow_dispatch as a backup strategy for attaching artifacts to a release when the tag-triggered run fails or is blocked - docs: replace pipeline diagrams with mermaid, show draft release convergence from release.yml into codebuild upload, and include the manual dispatch backup path * feat: changelog-first release flow with release PR and auto-tagging Replace the post-release changelog workflow with a changelog-first flow: - release-pr.yml: generates CHANGELOG.md and opens a release PR - tag-on-merge.yml: auto-tags the merge commit when a release PR is merged - Delete changelog.yml (no longer needed) - Update cliff.toml with skip rule for changelog noise and bump config - Update release.yml header comment to reference new flow - Update ADMINISTRATIVE_GUIDE.md with new architecture, diagrams, and process * feat: validate semver format on release-pr version input Reject version inputs that don't match strict MAJOR.MINOR.PATCH format. * feat: graceful fallback when no conventional commits detected Fall back to patch bump from latest tag instead of failing. If no tags exist at all, exit cleanly with a warning instead of failing the workflow. * fix: only apply release label if it exists in the repo * fix: dispatch release and codebuild workflows explicitly after tagging Tags created via GITHUB_TOKEN don't trigger push events in other workflows. Use gh workflow run with --ref to dispatch release.yml and codebuild.yml directly, which is exempt from this limitation. * docs: update admin guide for workflow dispatch pattern Update diagram, workflow reference, permissions, and release process to reflect that tag-on-merge.yml dispatches release.yml and codebuild.yml explicitly via workflow_dispatch rather than relying on tag push events. * fix: add --repo flag to gh workflow run commands The tag-on-merge job has no checkout step, so gh needs an explicit --repo to know which repository to dispatch workflows in. * fix: skip release gracefully when dispatched from a branch Show a warning annotation and skip remaining steps instead of failing with a confusing error when the workflow runs on a non-tag ref. * docs: fix admin guide accuracy for recent workflow changes - Release PR: document semver validation, graceful fallback, conditional label - Tag Release: add --repo to dispatch step descriptions - CodeBuild: mention dispatch from tag-on-merge.yml in trigger description - Release: document graceful skip when dispatched from a branch - Security Posture: add injection-safe input patterns * style: align markdown table columns in admin guide * feat: wait for draft release before dispatching codebuild Dispatch release.yml first and watch it to completion before dispatching codebuild.yml. This ensures the draft release exists before build artifacts are uploaded. Falls back to dispatching codebuild anyway if the release run can't be found or doesn't succeed. * docs: update admin guide for sequential release-then-codebuild dispatch * fix: skip gracefully when release branch already exists Check for existing remote branch before attempting to create it. Exits cleanly with a warning annotation pointing the user to the existing PR instead of failing on git checkout -b. * docs: document release branch existence check in admin guide * fix: simplify mermaid diagram node text to fix rendering Remove special characters (--ref, --bumped-version) from node labels that mermaid may interpret as link syntax. Fix ambiguous dual outgoing edges from release.yml node. * fix: use ::warning:: annotation for no-tags-exist case --------- Co-authored-by: Scott Schreckengaust <345885+scottschreckengaust@users.noreply.github.com>
1 parent 4f9292a commit 5ffc938

File tree

7 files changed

+744
-68
lines changed

7 files changed

+744
-68
lines changed

.github/workflows/changelog.yml

Lines changed: 0 additions & 59 deletions
This file was deleted.

.github/workflows/codebuild.yml

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ on:
55
push:
66
branches:
77
- main
8+
tags:
9+
- 'v*'
810

911
concurrency:
1012
group: ${{ github.workflow }}-${{ github.ref }}
@@ -36,7 +38,7 @@ jobs:
3638

3739
permissions:
3840
actions: write
39-
contents: read
41+
contents: write
4042
id-token: write # Required for OIDC token request to AWS STS
4143

4244
runs-on: ubuntu-latest
@@ -203,3 +205,51 @@ jobs:
203205
name: trend.zip
204206
path: ${{ github.workspace }}/.codebuild/downloads/trend.zip
205207
if-no-files-found: error
208+
209+
- name: Upload artifacts to release
210+
if: startsWith(github.ref, 'refs/tags/v')
211+
env:
212+
GH_TOKEN: ${{ github.token }}
213+
TAG: ${{ github.ref_name }}
214+
REPO: ${{ github.repository }}
215+
run: |
216+
DOWNLOADS="${GITHUB_WORKSPACE}/.codebuild/downloads"
217+
ARTIFACTS=(
218+
"$DOWNLOADS/${{ env.CODEBUILD_PROJECT_NAME }}.zip"
219+
"$DOWNLOADS/evaluation.zip"
220+
"$DOWNLOADS/trend.zip"
221+
)
222+
223+
# Wait for release to exist (release.yml typically finishes in ~30s,
224+
# CodeBuild takes minutes — this is a safety net)
225+
RELEASE_EXISTS=false
226+
for i in $(seq 1 30); do
227+
if gh release view "$TAG" --repo "$REPO" --json isDraft,tagName &>/dev/null; then
228+
RELEASE_EXISTS=true
229+
break
230+
fi
231+
echo "Waiting for release $TAG (attempt $i/30)..."
232+
sleep 10
233+
done
234+
235+
if [[ "$RELEASE_EXISTS" == "true" ]]; then
236+
# Release exists (draft or published) — upload/replace artifacts
237+
IS_DRAFT=$(gh release view "$TAG" --repo "$REPO" --json isDraft --jq '.isDraft')
238+
if [[ "$IS_DRAFT" == "true" ]]; then
239+
echo "Draft release $TAG found — uploading artifacts"
240+
else
241+
echo "Published release $TAG found — attempting to replace artifacts"
242+
fi
243+
gh release upload "$TAG" "${ARTIFACTS[@]}" --repo "$REPO" --clobber || {
244+
echo "WARNING: Failed to upload artifacts to release $TAG (release may be immutable)"
245+
echo "Artifacts are still available as workflow artifacts above"
246+
}
247+
else
248+
# No release exists — create a draft with artifacts
249+
echo "No release found for $TAG — creating draft release with artifacts"
250+
gh release create "$TAG" "${ARTIFACTS[@]}" \
251+
--repo "$REPO" \
252+
--draft \
253+
--title "AI-DLC Workflow ${TAG#v}" \
254+
--notes "Build artifacts from CodeBuild. Rules zip pending from release workflow."
255+
fi

.github/workflows/release-pr.yml

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Release PR
2+
#
3+
# Creates a PR with an updated CHANGELOG.md for a new release.
4+
# The changelog is generated from conventional commits using git-cliff.
5+
#
6+
# When the PR is merged, tag-on-merge.yml automatically tags the merge commit,
7+
# which triggers release.yml (draft release) and codebuild.yml (build artifacts).
8+
#
9+
# Usage:
10+
# 1. Run this workflow via workflow_dispatch (optionally specify a version)
11+
# 2. Review and merge the resulting PR
12+
# 3. The tag is created automatically — review and publish the draft release
13+
14+
name: Release PR
15+
16+
on:
17+
workflow_dispatch:
18+
inputs:
19+
version:
20+
description: 'Release version (e.g., 0.2.0). Leave empty to auto-determine from conventional commits.'
21+
required: false
22+
type: string
23+
24+
permissions:
25+
contents: write
26+
pull-requests: write
27+
28+
jobs:
29+
release-pr:
30+
name: Create Release PR
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
35+
with:
36+
fetch-depth: 0
37+
38+
- name: Install git-cliff
39+
uses: orhun/git-cliff-action@e16f179f0be49ecdfe63753837f20b9531642772 # v4.7.0
40+
with:
41+
config: cliff.toml
42+
args: --version
43+
env:
44+
OUTPUT: /dev/null
45+
46+
- name: Determine version
47+
id: version
48+
env:
49+
INPUT_VERSION: ${{ inputs.version }}
50+
run: |
51+
if [[ -n "$INPUT_VERSION" ]]; then
52+
# Strip leading v if present for validation
53+
VERSION="${INPUT_VERSION#v}"
54+
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
55+
echo "ERROR: Version '$INPUT_VERSION' is not valid semver (expected: MAJOR.MINOR.PATCH, e.g. 0.2.0)"
56+
exit 1
57+
fi
58+
else
59+
VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "")
60+
if [[ -z "$VERSION" ]]; then
61+
# Fall back to patch bump from latest tag
62+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
63+
if [[ -n "$LATEST_TAG" ]]; then
64+
LATEST="${LATEST_TAG#v}"
65+
MAJOR="${LATEST%%.*}"
66+
REST="${LATEST#*.}"
67+
MINOR="${REST%%.*}"
68+
PATCH="${REST#*.}"
69+
PATCH=$((PATCH + 1))
70+
VERSION="${MAJOR}.${MINOR}.${PATCH}"
71+
echo "WARNING: No conventional commits detected — falling back to patch bump: $VERSION"
72+
else
73+
echo "::warning::No conventional commits and no existing tags — nothing to release"
74+
exit 0
75+
fi
76+
fi
77+
fi
78+
# Strip leading v if present
79+
VERSION="${VERSION#v}"
80+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
81+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
82+
echo "Determined version: $VERSION (tag: v$VERSION)"
83+
84+
- name: Check tag does not exist
85+
env:
86+
TAG: ${{ steps.version.outputs.tag }}
87+
run: |
88+
if git rev-parse "refs/tags/$TAG" &>/dev/null; then
89+
echo "ERROR: Tag $TAG already exists"
90+
exit 1
91+
fi
92+
93+
- name: Generate changelog
94+
uses: orhun/git-cliff-action@e16f179f0be49ecdfe63753837f20b9531642772 # v4.7.0
95+
with:
96+
config: cliff.toml
97+
args: --tag ${{ steps.version.outputs.tag }}
98+
env:
99+
OUTPUT: CHANGELOG.md
100+
GITHUB_REPO: ${{ github.repository }}
101+
102+
- name: Create release PR
103+
env:
104+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105+
VERSION: ${{ steps.version.outputs.version }}
106+
TAG: ${{ steps.version.outputs.tag }}
107+
run: |
108+
BRANCH="release/$TAG"
109+
110+
# Check if branch already exists (local or remote)
111+
if git ls-remote --exit-code --heads origin "$BRANCH" &>/dev/null; then
112+
echo "::warning::Branch '$BRANCH' already exists. A release PR may already be open — close it and delete the branch to re-run."
113+
exit 0
114+
fi
115+
116+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
117+
git config --local user.name "github-actions[bot]"
118+
119+
git add CHANGELOG.md
120+
if git diff --cached --quiet CHANGELOG.md; then
121+
echo "No changes to CHANGELOG.md"
122+
exit 0
123+
fi
124+
125+
git checkout -b "$BRANCH"
126+
git commit -m "docs: update changelog for $TAG"
127+
git push origin "$BRANCH"
128+
129+
LABEL_FLAG=""
130+
if gh label list --search "release" --json name --jq '.[].name' | grep -qx "release"; then
131+
LABEL_FLAG="--label release"
132+
fi
133+
134+
gh pr create \
135+
--title "release: $TAG" \
136+
--body "$(cat <<EOF
137+
## Release $TAG
138+
139+
This PR updates CHANGELOG.md for the $TAG release.
140+
141+
**When merged**, the merge commit will be automatically tagged as \`$TAG\`, which triggers:
142+
- \`release.yml\` — creates a draft GitHub Release with the rules zip
143+
- \`codebuild.yml\` — runs CodeBuild and attaches build artifacts to the draft
144+
145+
After both workflows complete, review and publish the draft release.
146+
EOF
147+
)" \
148+
$LABEL_FLAG

.github/workflows/release.yml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
# Release Pipeline
22
#
33
# This workflow handles versioning and distribution of the AI-DLC methodology.
4-
# It triggers when you push a version tag and:
4+
# It triggers when a version tag is pushed (by tag-on-merge.yml or manually) and:
55
#
66
# 1. Creates distribution artifact:
77
# - ai-dlc-rules-vX.X.X.zip (Rules format for Amazon Q, Kiro, etc.)
8-
# 2. Publishes a GitHub Release with artifact attached
8+
# 2. Creates a draft GitHub Release with artifact attached
99
#
10-
# Note: Changelog is generated separately by changelog.yml after release is published.
10+
# The release is created as a draft so that build artifacts from CodeBuild
11+
# (codebuild.yml) can be attached before a human reviews and publishes it.
1112
#
12-
# Usage:
13-
# git tag v1.2.0
14-
# git push origin v1.2.0
13+
# Normal flow:
14+
# 1. release-pr.yml creates a PR with CHANGELOG update (manual dispatch)
15+
# 2. Human reviews and merges the release PR
16+
# 3. tag-on-merge.yml auto-tags the merge commit → triggers this workflow
1517

1618
name: Release
1719

1820
on:
21+
workflow_dispatch: {}
1922
push:
2023
tags:
2124
- 'v*'
@@ -37,18 +40,26 @@ jobs:
3740
- name: Extract version
3841
id: version
3942
run: |
43+
if [[ "$GITHUB_REF" != refs/tags/v* ]]; then
44+
echo "::warning::Skipping release — this workflow must run on a v* tag (got $GITHUB_REF). Use workflow_dispatch from a tag, not a branch."
45+
echo "skip=true" >> "$GITHUB_OUTPUT"
46+
exit 0
47+
fi
4048
VERSION=${GITHUB_REF#refs/tags/v}
41-
echo "version=$VERSION" >> $GITHUB_OUTPUT
42-
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
49+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
50+
echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
4351
4452
- name: Create release artifact
53+
if: steps.version.outputs.skip != 'true'
4554
run: |
4655
VERSION="${{ steps.version.outputs.version }}"
4756
zip -r "ai-dlc-rules-v${VERSION}.zip" aidlc-rules/
4857
4958
- name: Create GitHub Release
59+
if: steps.version.outputs.skip != 'true'
5060
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
5161
with:
62+
draft: true
5263
name: "AI-DLC Workflow v${{ steps.version.outputs.version }}"
5364
body: |
5465
Release v${{ steps.version.outputs.version }}

0 commit comments

Comments
 (0)