Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 0 additions & 216 deletions .github/workflows/manual-release.yaml

This file was deleted.

167 changes: 167 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: Publish

on:
push:
branches: [main]
paths:
- "src/**"
- "package.json"
- "package-lock.json"
Comment on lines +3 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stable release commit will re-trigger the workflow as a next prerelease.

The push trigger watches package.json and package-lock.json. After a latest run commits the version bump and pushes to main (line 145), the resulting push matches this filter and spawns a new run that goes down the prerelease path — immediately publishing e.g. 1.13.2-next.0 right after 1.13.2 stable. If that’s intentional (auto‑resume next track), consider documenting it; otherwise, guard the push trigger to skip release commits, e.g. by checking the commit author/message in the job if, or use [skip ci] in the commit message on line 140.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish.yaml around lines 3 - 9, The push trigger for the
publish workflow currently watches package.json and package-lock.json and will
retrigger when the release job commits the version bump; update the workflow to
skip release-commit pushes by adding a guard: in the publish workflow's jobs add
an `if` check that ignores runs when the commit author or actor is the GitHub
Actions bot (e.g., github.actor == 'github-actions[bot]') or when the head
commit message contains a skip token (e.g., "[skip ci]"); modify the release
step that performs the version bump (the job that pushes the new version) to
either use a skip token in its commit message or set the actor so the guard
matches, ensuring the stable release push does not re-enter the prerelease path.

workflow_dispatch:
inputs:
channel:
description: "Publish channel"
required: true
default: "latest"
type: choice
options:
- latest
- next
bump:
description: "Version bump type (latest channel only)"
required: false
default: "patch"
type: choice
options:
- patch
- minor
- major

concurrency:
group: publish
cancel-in-progress: ${{ github.event_name == 'push' }}
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concurrency.group: publish combined with cancel-in-progress for push runs means a new push can cancel an in-flight manual channel=latest release run, which can leave a partially completed release sequence.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment on lines +30 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Push events can cancel an in-progress stable release.

concurrency.group is a single publish group and cancel-in-progress evaluates to true whenever the new run is a push. If a maintainer triggers a workflow_dispatch with channel=latest and, while it is running, any push to main that touches src/**, package.json, or package-lock.json lands (including the release commit from another branch or a concurrent merge), the incoming push run will cancel the in-progress stable release — potentially between npm publish --tag latest and the git push/gh release create steps, leaving npm and git out of sync.

Consider either separating the groups by channel or never cancelling when a latest release is in flight:

Suggested change
concurrency:
group: publish
cancel-in-progress: ${{ github.event_name == 'push' }}
concurrency:
group: publish-${{ github.event_name == 'workflow_dispatch' && inputs.channel || 'next' }}
cancel-in-progress: ${{ github.event_name == 'push' }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish.yaml around lines 30 - 32, The current concurrency
setup uses a single group named "publish" with cancel-in-progress true for
pushes, which allows a push run to cancel an in-progress workflow_dispatch
release; change the concurrency grouping so runs are isolated by event/channel
(e.g., include the event and inputs.channel in concurrency.group) or disable
cancel-in-progress for workflow_dispatch releases so a manually-triggered
"latest" publish isn't cancelled by a push; update the concurrency.group and
cancel-in-progress expression (the concurrency.group and cancel-in-progress keys
in the workflow) to reflect this separation.


permissions:
id-token: write
contents: write

jobs:
publish:
if: github.repository == 'bmad-code-org/bmad-method-test-architecture-enterprise' && (github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main')
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App token
id: app-token
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For npm Trusted Publishing, it’s common/expected to set registry-url: https://registry.npmjs.org in actions/setup-node; without it, publishes can fail depending on runner/npm configuration.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


- name: Ensure trusted publishing toolchain
run: |
# npm trusted publishing requires modern npm on the runner.
npm install --global npm@11.6.2

- name: Configure git user
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

- name: Derive next prerelease version
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.channel == 'next')
run: |
NEXT_VER=$(npm view bmad-method-test-architecture-enterprise@next version 2>/dev/null || echo "")
LATEST_VER=$(npm view bmad-method-test-architecture-enterprise@latest version 2>/dev/null || echo "")

BASE=$(node -e "
const semver = require('semver');
const next = process.argv[1] || null;
const latest = process.argv[2] || null;
if (!next && !latest) process.exit(0);
if (!next) { console.log(latest); process.exit(0); }
if (!latest) { console.log(next); process.exit(0); }
const nextBase = next.replace(/-next\.\d+$/, '');
console.log(semver.gt(latest, nextBase) ? latest : next);
" "$NEXT_VER" "$LATEST_VER")

if [ -n "$BASE" ]; then
npm version "$BASE" --no-git-tag-version --allow-same-version
fi
npm version prerelease --preid=next --no-git-tag-version
Comment on lines +81 to +101
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Verify semver comparison logic
# Check if 1.13.1-next.6 < 1.13.1
python3 <<'EOF'
import subprocess
import json

# Test semver comparisons in Node.js
test_script = """
const semver = require('semver');

// Test 1: Is 1.13.1-next.6 less than 1.13.1?
console.log('1.13.1-next.6 < 1.13.1:', semver.lt('1.13.1-next.6', '1.13.1'));

// Test 2: Verify gt vs gte with equal versions
const latest = '1.13.1';
const nextBase = '1.13.1';
console.log('semver.gt("1.13.1", "1.13.1"):', semver.gt(latest, nextBase));
console.log('semver.gte("1.13.1", "1.13.1"):', semver.gte(latest, nextBase));

// Test 3: Verify the logic flow when latest equals nextBase
if (!semver.gt(latest, nextBase)) {
  console.log('With gt: BASE would be set to next (1.13.1-next.5)');
}
if (semver.gte(latest, nextBase)) {
  console.log('With gte: BASE would be set to latest (1.13.1)');
}

// Test 4: Check other comparisons
console.log('semver.gt("1.13.2", "1.13.1"):', semver.gt('1.13.2', '1.13.1'));
console.log('semver.lt("1.13.0", "1.13.1"):', semver.lt('1.13.0', '1.13.1'));
"""

console.log("=== Testing semver comparison logic ===")
print(test_script)
EOF

Repository: bmad-code-org/bmad-method-test-architecture-enterprise

Length of output: 212


🏁 Script executed:

#!/bin/bash

# Run the semver tests
node -e "
const semver = require('semver');

console.log('=== Semver Comparison Tests ===');
console.log('');

// Test 1: Is prerelease older than stable?
console.log('Test 1: Prerelease vs stable');
console.log('semver.lt(\"1.13.1-next.6\", \"1.13.1\"):', semver.lt('1.13.1-next.6', '1.13.1'));
console.log('');

// Test 2: gt vs gte with equal base versions
console.log('Test 2: gt vs gte comparison');
const latest = '1.13.1';
const nextBase = '1.13.1';
console.log('semver.gt(\"' + latest + '\", \"' + nextBase + '\"):', semver.gt(latest, nextBase));
console.log('semver.gte(\"' + latest + '\", \"' + nextBase + '\"):', semver.gte(latest, nextBase));
console.log('');

// Test 3: Impact on BASE selection
console.log('Test 3: Impact on BASE selection logic');
console.log('When latest=\"1.13.1\" and nextBase=\"1.13.1\":');
console.log('  gt(latest, nextBase) returns:', semver.gt(latest, nextBase), '-> BASE would be next');
console.log('  gte(latest, nextBase) returns:', semver.gte(latest, nextBase), '-> BASE would be latest');
"

Repository: bmad-code-org/bmad-method-test-architecture-enterprise

Length of output: 949


🏁 Script executed:

#!/bin/bash

# Test npm version prerelease behavior
# We'll create a test to understand the behavior
npm --version
node -e "console.log('Node:', process.version)"

# Create a temporary directory to test npm version behavior
TMPDIR=$(mktemp -d)
cd "$TMPDIR"

# Initialize a test package
npm init -y > /dev/null 2>&1

echo "=== Testing npm version prerelease behavior ==="
echo ""

# Test 1: Starting from a stable version
echo "Test 1: npm version prerelease --preid=next on a clean stable"
npm pkg set version="1.13.1"
npm version prerelease --preid=next --no-git-tag-version 2>&1 | grep -oP '(?<=>)[^<]+' || npm pkg get version

# Test 2: Starting from a prerelease
npm pkg set version="1.13.1-next.5"
echo ""
echo "Test 2: npm version prerelease --preid=next on 1.13.1-next.5"
npm version prerelease --preid=next --no-git-tag-version 2>&1 | grep -oP '(?<=>)[^<]+' || npm pkg get version

# Clean up
cd /
rm -rf "$TMPDIR"

Repository: bmad-code-org/bmad-method-test-architecture-enterprise

Length of output: 325


🏁 Script executed:

#!/bin/bash

# Simpler test for npm version behavior
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
cd "$TMPDIR"

npm init -y > /dev/null 2>&1

echo "=== npm version prerelease Tests ==="
echo ""

# Test 1: Clean stable to prerelease
echo "Test 1: From clean stable (1.13.1) to prerelease"
npm pkg set version="1.13.1"
echo "Before: $(npm pkg get version)"
npm version prerelease --preid=next --no-git-tag-version > /dev/null 2>&1
echo "After prerelease: $(npm pkg get version)"
echo ""

# Test 2: From prerelease to next prerelease  
npm pkg set version="1.13.1-next.5"
echo "Test 2: From prerelease (1.13.1-next.5) to next prerelease"
echo "Before: $(npm pkg get version)"
npm version prerelease --preid=next --no-git-tag-version > /dev/null 2>&1
echo "After prerelease: $(npm pkg get version)"
echo ""

# Test 3: From 1.13.2-next.0 (simulating after patch bump)
npm pkg set version="1.13.2-next.0"
echo "Test 3: From patch-bumped prerelease (1.13.2-next.0)"
echo "Before: $(npm pkg get version)"
npm version prerelease --preid=next --no-git-tag-version > /dev/null 2>&1
echo "After prerelease: $(npm pkg get version)"

Repository: bmad-code-org/bmad-method-test-architecture-enterprise

Length of output: 465


🏁 Script executed:

npm install semver --no-save && node -e "
const semver = require('semver');

console.log('=== Semver Comparison Verification ===');
console.log('');

// Core claim: prerelease is older than stable
console.log('Prerelease ordering:');
console.log('semver.lt(\"1.13.1-next.6\", \"1.13.1\"):', semver.lt('1.13.1-next.6', '1.13.1'));
console.log('');

// Test the specific condition: gt vs gte when versions are equal
console.log('Comparison when latest == nextBase:');
const latest = '1.13.1';
const nextBase = '1.13.1';
console.log('latest=\"' + latest + '\", nextBase=\"' + nextBase + '\"');
console.log('semver.gt(latest, nextBase):', semver.gt(latest, nextBase), '-> BASE would be: next');
console.log('semver.gte(latest, nextBase):', semver.gte(latest, nextBase), '-> BASE would be: latest');
console.log('');

// Show impact: what happens with each choice
console.log('Impact analysis:');
console.log('If BASE=next (gt returns false):');
console.log('  Starting: 1.13.1-next.5');
console.log('  After npm version prerelease --preid=next: 1.13.1-next.6');
console.log('  Semver comparison: 1.13.1-next.6 < 1.13.1:', semver.lt('1.13.1-next.6', '1.13.1'));
console.log('');
console.log('If BASE=latest (gte returns true):');
console.log('  Starting: 1.13.1');
console.log('  After npm version prerelease --preid=next: 1.13.2-next.0');
console.log('  Semver comparison: 1.13.2-next.0 >= 1.13.1:', semver.gte('1.13.2-next.0', '1.13.1'));
"

Repository: bmad-code-org/bmad-method-test-architecture-enterprise

Length of output: 3284


Change semver.gt to semver.gte to prevent prerelease regression after stable promotion.

When latest equals nextBase (the common case right after promoting a stable release), semver.gt(latest, nextBase) returns false, causing BASE=next (e.g., 1.13.1-next.5). Then npm version prerelease --preid=next yields 1.13.1-next.6, which semver considers older than the now-published stable 1.13.1. Consumers on @next who have @latest installed can end up pinned to an older build, and the next channel silently stops tracking forward progress.

Using gte ensures that when latest equals nextBase, BASE becomes latest, so npm version prerelease --preid=next on 1.13.1 produces 1.13.2-next.0, correctly advancing the prerelease.

Suggested change
Suggested change
- name: Derive next prerelease version
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.channel == 'next')
run: |
NEXT_VER=$(npm view bmad-method-test-architecture-enterprise@next version 2>/dev/null || echo "")
LATEST_VER=$(npm view bmad-method-test-architecture-enterprise@latest version 2>/dev/null || echo "")
BASE=$(node -e "
const semver = require('semver');
const next = process.argv[1] || null;
const latest = process.argv[2] || null;
if (!next && !latest) process.exit(0);
if (!next) { console.log(latest); process.exit(0); }
if (!latest) { console.log(next); process.exit(0); }
const nextBase = next.replace(/-next\.\d+$/, '');
console.log(semver.gt(latest, nextBase) ? latest : next);
" "$NEXT_VER" "$LATEST_VER")
if [ -n "$BASE" ]; then
npm version "$BASE" --no-git-tag-version --allow-same-version
fi
npm version prerelease --preid=next --no-git-tag-version
BASE=$(node -e "
const semver = require('semver');
const next = process.argv[1] || null;
const latest = process.argv[2] || null;
if (!next && !latest) process.exit(0);
if (!next) { console.log(latest); process.exit(0); }
if (!latest) { console.log(next); process.exit(0); }
const nextBase = next.replace(/-next\.\d+$/, '');
console.log(semver.gte(latest, nextBase) ? latest : next);
" "$NEXT_VER" "$LATEST_VER")
if [ -n "$BASE" ]; then
npm version "$BASE" --no-git-tag-version --allow-same-version
fi
npm version prerelease --preid=next --no-git-tag-version

Note: when BASE=latest (stable), npm version prerelease --preid=next on 1.13.1 produces 1.13.2-next.0 — please verify this matches your expectations for the first prerelease after a stable release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish.yaml around lines 80 - 100, Replace the semver
comparison in the inline Node script that computes BASE so it uses semver.gte
instead of semver.gt; specifically, in the Node block that defines next, latest
and nextBase (the code that currently does console.log(semver.gt(latest,
nextBase) ? latest : next)), change that call to semver.gte to ensure BASE
becomes latest when latest === nextBase, then keep the existing npm version
"$BASE" and prerelease steps unchanged.


- name: Bump stable version
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
run: npm version "${{ inputs.bump }}" --no-git-tag-version

- name: Sync marketplace version
run: |
node <<'NODE'
const fs = require('node:fs');

const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const marketplacePath = '.claude-plugin/marketplace.json';
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8'));
const plugin = (marketplace.plugins || []).find((entry) => entry && entry.name === packageJson.name);

if (!plugin) {
throw new Error(`Marketplace entry not found for ${packageJson.name}`);
}

plugin.version = packageJson.version;
fs.writeFileSync(marketplacePath, `${JSON.stringify(marketplace, null, 2)}\n`);
NODE

- name: Validate release metadata
run: npm run test:release-metadata

- name: Publish prerelease to npm
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.channel == 'next')
run: npm publish --tag next --provenance

- name: Publish stable release to npm
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
run: npm publish --tag latest --provenance
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stable path publishes to npm before the version bump commit/tag is created and pushed; if the later git push/tag/release steps fail, npm can contain a version that isn’t represented in git and reruns may hit “version already exists”.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


- name: Commit version bump
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
run: |
VERSION=$(node -p 'require("./package.json").version')
git add package.json package-lock.json .claude-plugin/marketplace.json
git commit -m "release: bump to v${VERSION}"
git tag -a "v${VERSION}" -m "Release v${VERSION}"

- name: Push version commit and tag
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
run: git push origin main --follow-tags
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stable publish happens before the git push — failure leaves npm ahead of main.

For the latest channel, the order is:

  1. npm publish --tag latest --provenance (line 133)
  2. git commit + git tag (lines 140‑141)
  3. git push origin main --follow-tags (line 145)

If step 3 fails (branch protection, missing app permissions, race with another push, etc.), the version is already live on npm with no corresponding commit/tag on main. A subsequent run will re-bump from the stale package.json version and attempt to publish a version that’s already taken, and the v${VERSION} tag never exists.

Prefer committing, tagging, and pushing first, then publishing to npm. The npm publish step is the hardest to reverse (unpublish has a 72‑hour window and deprecation is permanent), so it should be the last irreversible action. Alternatively, keep the current order but add a guard at the top of the workflow that fails fast if the bot/app cannot push to main.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish.yaml around lines 131 - 145, The workflow
publishes to npm before pushing the version commit and tag, risking an
irreversible publish without a corresponding commit/tag on main; change the
sequence so the steps "Commit version bump" (creates v${VERSION}) and "Push
version commit and tag" (git push origin main --follow-tags) run before the
"Publish stable release to npm" (npm publish --tag latest --provenance), or
alternatively add a guard step at the top that verifies the runner/bot can push
to main (e.g., attempt a dry-run git push or check required permissions) and
fail fast if push will be blocked; ensure the step names "Commit version bump",
"Push version commit and tag", and the npm publish invocation are updated
accordingly so publish is the last irreversible action.


- name: Create GitHub Release
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
run: |
TAG="v$(node -p 'require("./package.json").version')"
VERSION="${TAG#v}"
BODY=$(awk -v ver="$VERSION" '
/^## / {
if (found) exit
if ($0 ~ "^## \\[" ver "\\]" || $0 ~ "^## v" ver) found=1
next
}
found { print }
' CHANGELOG.md)
if [ -z "$BODY" ]; then
echo "::warning::No CHANGELOG.md entry for $TAG — falling back to auto-generated notes"
gh release create "$TAG" --generate-notes
else
gh release create "$TAG" --notes "$BODY"
fi
Comment on lines +153 to +166
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

CHANGELOG header matcher is narrow.

The awk pattern matches only ## [1.2.3] or ## v1.2.3. Common Keep-a-Changelog variants like ## 1.2.3 or ## [1.2.3] - 2026-04-23 with surrounding whitespace work for the second form but a bare ## 1.2.3 (no brackets, no v) silently falls through to the auto-generated notes path. If the project’s CHANGELOG.md style doesn’t include brackets/v, every release will use fallback notes without any warning that a matching entry actually existed in a slightly different format.

Consider broadening the pattern, or at minimum documenting the required header format in CHANGELOG.md / the release guide.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish.yaml around lines 152 - 165, The awk matcher that
builds BODY (the multi-line awk block using VERSION) is too narrow and only
matches "## [ver]" or "## vver"; update that awk pattern to accept common header
variants (e.g., "## ver", "## [ver]", "## vver" and headers with an optional
date or suffix like " - YYYY-MM-DD") so the block correctly sets found when
encountering headers for TAG/VERSION; change the regex inside the awk script
that checks "^## \\[" ver "\\]" || "^## v" ver to a single broader test that
allows optional brackets, optional leading "v", optional surrounding whitespace,
and optional trailing date/suffix, and keep using the same BODY, VERSION, TAG
variables for subsequent gh release create invocation.

env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading