From a673bf658fea6971a0b0ed0020738c03625e4cc1 Mon Sep 17 00:00:00 2001 From: Volodymyr Vreshch Date: Tue, 11 Aug 2026 01:41:16 +0200 Subject: [PATCH 1/5] ci: rename publish.yml -> release.yml --- .github/workflows/release.yml | 206 ++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1ada0c1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,206 @@ +name: Publish Package + +on: + push: + branches: [master, main] + paths: + - 'package.json' + workflow_dispatch: + +permissions: + contents: write + id-token: write + +env: + NODE_VERSION: '22' + +jobs: + # ========================================================================= + # GATE — only publish from release-prepare workflow (release/* branch merges) + # ========================================================================= + release-gate: + name: 🔒 Release Gate + runs-on: ubuntu-latest + outputs: + is-release: ${{ steps.check.outputs.is-release }} + steps: + - name: đŸ“Ĩ Checkout code + uses: actions/checkout@v7 + with: + fetch-depth: 5 + + - name: 🔒 Check if this is a release commit + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # For workflow_dispatch, always allow + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "is-release=true" >> $GITHUB_OUTPUT + echo "✅ Manual trigger — allowed" + exit 0 + fi + + # Check if the merge commit came from a release/* branch + COMMIT_MSG=$(git log -1 --pretty=%s) + echo "Commit message: $COMMIT_MSG" + + # Match squash merges from release/* branches or release prepare commits + # Match: merge commits from release/*, squash merges (PR title "Release v..."), or release prepare commits + if echo "$COMMIT_MSG" | grep -qE "(^Merge pull request .* from .*/release/|^Release v|^chore: prepare release|^chore\(release\))"; then + echo "is-release=true" >> $GITHUB_OUTPUT + echo "✅ Release commit detected — proceeding with publish" + else + echo "is-release=false" >> $GITHUB_OUTPUT + echo "⛔ Not a release commit — skipping publish" + echo "💡 All releases must go through the release-prepare workflow" + fi + + detect-changes: + name: 🔍 Detect Version Changes + needs: [release-gate] + if: needs.release-gate.outputs.is-release == 'true' + runs-on: ubuntu-latest + outputs: + should-publish: ${{ steps.detect.outputs.should-publish }} + name: ${{ steps.detect.outputs.name }} + version: ${{ steps.detect.outputs.version }} + + steps: + - name: đŸ“Ĩ Checkout code + uses: actions/checkout@v7 + + - name: 🔧 Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: đŸ“Ļ Install dependencies + run: npm ci + + - name: 🔍 Detect version changes + id: detect + run: | + name=$(node -p "require('./package.json').name") + version=$(node -p "require('./package.json').version") + private=$(node -p "require('./package.json').private || false") + + echo "name=${name}" >> $GITHUB_OUTPUT + echo "version=${version}" >> $GITHUB_OUTPUT + + if [ "$private" = "true" ]; then + echo "should-publish=false" >> $GITHUB_OUTPUT + echo "â„šī¸ Package is private, skipping publish" + exit 0 + fi + + echo "đŸ“Ļ Checking ${name}@${version}..." + + if npm view "${name}@${version}" version 2>/dev/null; then + echo "should-publish=false" >> $GITHUB_OUTPUT + echo "â­ī¸ Version ${version} already published for ${name}" + else + echo "should-publish=true" >> $GITHUB_OUTPUT + echo "✨ New version detected: ${name}@${version}" + fi + + verify: + name: ✅ Verify Package + needs: [detect-changes] + if: needs.detect-changes.outputs.should-publish == 'true' + runs-on: ubuntu-latest + + steps: + - name: đŸ“Ĩ Checkout code + uses: actions/checkout@v7 + + - name: 🔧 Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: đŸ“Ļ Install dependencies + run: npm ci + + - name: ✅ Verify + run: npm run verify + + publish: + name: 📤 Publish Package + needs: [detect-changes, verify] + if: needs.detect-changes.outputs.should-publish == 'true' && needs.verify.result == 'success' + runs-on: ubuntu-latest + + steps: + - name: đŸ“Ĩ Checkout code + uses: actions/checkout@v7 + + - name: 🔧 Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + + - name: đŸ“Ļ Install dependencies + run: npm ci + + - name: đŸ—ī¸ Build package + run: npm run build + + - name: 📤 Publish to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + echo "📤 Publishing ${{ needs.detect-changes.outputs.name }}@${{ needs.detect-changes.outputs.version }}" + npm publish --access public --provenance + + - name: đŸˇī¸ Create git tag + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + TAG_NAME="v${{ needs.detect-changes.outputs.version }}" + + git tag -a "${TAG_NAME}" -m "Release ${{ needs.detect-changes.outputs.name }}@${{ needs.detect-changes.outputs.version }}" + git push origin "${TAG_NAME}" + + - name: ✅ Published successfully + run: | + echo "🎉 Successfully published ${{ needs.detect-changes.outputs.name }}@${{ needs.detect-changes.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "đŸ“Ļ Package URL: https://www.npmjs.com/package/${{ needs.detect-changes.outputs.name }}/v/${{ needs.detect-changes.outputs.version }}" >> $GITHUB_STEP_SUMMARY + + summary: + name: 📋 Publish Summary + needs: [release-gate, detect-changes, verify, publish] + if: always() + runs-on: ubuntu-latest + + steps: + - name: 📊 Create summary + run: | + echo "## đŸ“Ļ Package Publishing Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.release-gate.outputs.is-release }}" != "true" ]; then + echo "⛔ **Blocked — not a release commit**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All releases must go through the \`release-prepare\` workflow." >> $GITHUB_STEP_SUMMARY + echo "Run: \`gh workflow run release-prepare.yml -f bump_type=patch\`" >> $GITHUB_STEP_SUMMARY + elif [ "${{ needs.detect-changes.outputs.should-publish }}" != "true" ]; then + echo "â„šī¸ **No new version to publish**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Version ${{ needs.detect-changes.outputs.version }} is already published or package is private." >> $GITHUB_STEP_SUMMARY + elif [ "${{ needs.verify.result }}" != "success" ]; then + echo "❌ **Verification failed - package not published**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please fix verification errors and try again." >> $GITHUB_STEP_SUMMARY + elif [ "${{ needs.publish.result }}" == "success" ]; then + echo "✅ **Package published successfully!**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **${{ needs.detect-changes.outputs.name }}** version ${{ needs.detect-changes.outputs.version }}" >> $GITHUB_STEP_SUMMARY + else + echo "âš ī¸ **Package failed to publish**" >> $GITHUB_STEP_SUMMARY + fi From cfe4a0b767cf3a5d64feb2eba5b394583e6078d1 Mon Sep 17 00:00:00 2001 From: Volodymyr Vreshch Date: Tue, 11 Aug 2026 01:41:17 +0200 Subject: [PATCH 2/5] ci: drop publish.yml (renamed) --- .github/workflows/publish.yml | 206 ---------------------------------- 1 file changed, 206 deletions(-) delete mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 1ada0c1..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,206 +0,0 @@ -name: Publish Package - -on: - push: - branches: [master, main] - paths: - - 'package.json' - workflow_dispatch: - -permissions: - contents: write - id-token: write - -env: - NODE_VERSION: '22' - -jobs: - # ========================================================================= - # GATE — only publish from release-prepare workflow (release/* branch merges) - # ========================================================================= - release-gate: - name: 🔒 Release Gate - runs-on: ubuntu-latest - outputs: - is-release: ${{ steps.check.outputs.is-release }} - steps: - - name: đŸ“Ĩ Checkout code - uses: actions/checkout@v7 - with: - fetch-depth: 5 - - - name: 🔒 Check if this is a release commit - id: check - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # For workflow_dispatch, always allow - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "is-release=true" >> $GITHUB_OUTPUT - echo "✅ Manual trigger — allowed" - exit 0 - fi - - # Check if the merge commit came from a release/* branch - COMMIT_MSG=$(git log -1 --pretty=%s) - echo "Commit message: $COMMIT_MSG" - - # Match squash merges from release/* branches or release prepare commits - # Match: merge commits from release/*, squash merges (PR title "Release v..."), or release prepare commits - if echo "$COMMIT_MSG" | grep -qE "(^Merge pull request .* from .*/release/|^Release v|^chore: prepare release|^chore\(release\))"; then - echo "is-release=true" >> $GITHUB_OUTPUT - echo "✅ Release commit detected — proceeding with publish" - else - echo "is-release=false" >> $GITHUB_OUTPUT - echo "⛔ Not a release commit — skipping publish" - echo "💡 All releases must go through the release-prepare workflow" - fi - - detect-changes: - name: 🔍 Detect Version Changes - needs: [release-gate] - if: needs.release-gate.outputs.is-release == 'true' - runs-on: ubuntu-latest - outputs: - should-publish: ${{ steps.detect.outputs.should-publish }} - name: ${{ steps.detect.outputs.name }} - version: ${{ steps.detect.outputs.version }} - - steps: - - name: đŸ“Ĩ Checkout code - uses: actions/checkout@v7 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v7 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: đŸ“Ļ Install dependencies - run: npm ci - - - name: 🔍 Detect version changes - id: detect - run: | - name=$(node -p "require('./package.json').name") - version=$(node -p "require('./package.json').version") - private=$(node -p "require('./package.json').private || false") - - echo "name=${name}" >> $GITHUB_OUTPUT - echo "version=${version}" >> $GITHUB_OUTPUT - - if [ "$private" = "true" ]; then - echo "should-publish=false" >> $GITHUB_OUTPUT - echo "â„šī¸ Package is private, skipping publish" - exit 0 - fi - - echo "đŸ“Ļ Checking ${name}@${version}..." - - if npm view "${name}@${version}" version 2>/dev/null; then - echo "should-publish=false" >> $GITHUB_OUTPUT - echo "â­ī¸ Version ${version} already published for ${name}" - else - echo "should-publish=true" >> $GITHUB_OUTPUT - echo "✨ New version detected: ${name}@${version}" - fi - - verify: - name: ✅ Verify Package - needs: [detect-changes] - if: needs.detect-changes.outputs.should-publish == 'true' - runs-on: ubuntu-latest - - steps: - - name: đŸ“Ĩ Checkout code - uses: actions/checkout@v7 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v7 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: đŸ“Ļ Install dependencies - run: npm ci - - - name: ✅ Verify - run: npm run verify - - publish: - name: 📤 Publish Package - needs: [detect-changes, verify] - if: needs.detect-changes.outputs.should-publish == 'true' && needs.verify.result == 'success' - runs-on: ubuntu-latest - - steps: - - name: đŸ“Ĩ Checkout code - uses: actions/checkout@v7 - - - name: 🔧 Setup Node.js - uses: actions/setup-node@v7 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - registry-url: 'https://registry.npmjs.org' - - - name: đŸ“Ļ Install dependencies - run: npm ci - - - name: đŸ—ī¸ Build package - run: npm run build - - - name: 📤 Publish to npm - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - echo "📤 Publishing ${{ needs.detect-changes.outputs.name }}@${{ needs.detect-changes.outputs.version }}" - npm publish --access public --provenance - - - name: đŸˇī¸ Create git tag - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - TAG_NAME="v${{ needs.detect-changes.outputs.version }}" - - git tag -a "${TAG_NAME}" -m "Release ${{ needs.detect-changes.outputs.name }}@${{ needs.detect-changes.outputs.version }}" - git push origin "${TAG_NAME}" - - - name: ✅ Published successfully - run: | - echo "🎉 Successfully published ${{ needs.detect-changes.outputs.name }}@${{ needs.detect-changes.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "đŸ“Ļ Package URL: https://www.npmjs.com/package/${{ needs.detect-changes.outputs.name }}/v/${{ needs.detect-changes.outputs.version }}" >> $GITHUB_STEP_SUMMARY - - summary: - name: 📋 Publish Summary - needs: [release-gate, detect-changes, verify, publish] - if: always() - runs-on: ubuntu-latest - - steps: - - name: 📊 Create summary - run: | - echo "## đŸ“Ļ Package Publishing Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ needs.release-gate.outputs.is-release }}" != "true" ]; then - echo "⛔ **Blocked — not a release commit**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "All releases must go through the \`release-prepare\` workflow." >> $GITHUB_STEP_SUMMARY - echo "Run: \`gh workflow run release-prepare.yml -f bump_type=patch\`" >> $GITHUB_STEP_SUMMARY - elif [ "${{ needs.detect-changes.outputs.should-publish }}" != "true" ]; then - echo "â„šī¸ **No new version to publish**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Version ${{ needs.detect-changes.outputs.version }} is already published or package is private." >> $GITHUB_STEP_SUMMARY - elif [ "${{ needs.verify.result }}" != "success" ]; then - echo "❌ **Verification failed - package not published**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Please fix verification errors and try again." >> $GITHUB_STEP_SUMMARY - elif [ "${{ needs.publish.result }}" == "success" ]; then - echo "✅ **Package published successfully!**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **${{ needs.detect-changes.outputs.name }}** version ${{ needs.detect-changes.outputs.version }}" >> $GITHUB_STEP_SUMMARY - else - echo "âš ī¸ **Package failed to publish**" >> $GITHUB_STEP_SUMMARY - fi From fd450293e067cea7b0fb9e271ef3f81cc72b00ec Mon Sep 17 00:00:00 2001 From: Volodymyr Vreshch Date: Tue, 11 Aug 2026 01:41:19 +0200 Subject: [PATCH 3/5] ci: rename release-train.yml -> train.yml --- .github/workflows/train.yml | 200 ++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 .github/workflows/train.yml diff --git a/.github/workflows/train.yml b/.github/workflows/train.yml new file mode 100644 index 0000000..0d50c59 --- /dev/null +++ b/.github/workflows/train.yml @@ -0,0 +1,200 @@ +name: Release Train + +# Fully automated Friday MINOR release of @agentage/cli to npm. No human step on +# the happy path. Weekly cadence, always a minor bump. +# +# Level 2 in the @agentage dependency chain: this repo depends on +# @agentage/memory-core AND @agentage/server-memory, which release earlier the +# same evening. L0 runs 19:00 UTC, L1 runs 20:00 UTC, this L2 train runs 21:00 +# UTC so it picks up their freshly published versions. Single plain UTC cron - +# no Prague/DST gate: if GitHub fires the cron late, the train simply departs +# late instead of being gated away. +# +# GITHUB_TOKEN caveats handled here: its branch pushes don't fire pr-validation on +# the release PR (the in-workflow `npm run verify` is the CI gate), and its merges +# don't fire publish.yml's push trigger - so after merging, this workflow +# dispatches publish.yml explicitly. +on: + schedule: + - cron: '0 21 * * 5' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + actions: write + +jobs: + dependabot-quiescence: + name: Wait for dependabot triage to finish + runs-on: ubuntu-latest + timeout-minutes: 50 + steps: + - name: Wait until this repo has no fresh open dependabot PRs + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + # Dependabot merging is owned by the org triage automation; this job only + # delays the train while that agent may still be working. PRs still open + # after the wait were deliberately left by triage - the train departs + # without them. + for i in $(seq 1 45); do + OPEN=$(gh pr list -R "$REPO" --author "app/dependabot" --state open --json number --jq 'length') + [ "$OPEN" = "0" ] && { echo "No open dependabot PRs - proceeding."; exit 0; } + echo "poll $i: $OPEN open dependabot PR(s), waiting for triage..." + sleep 60 + done + echo "Dependabot PR(s) still open after wait - releasing without them." | tee -a "$GITHUB_STEP_SUMMARY" + + release: + name: Cut minor release + needs: dependabot-quiescence + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: '22' + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Skip if an open release PR exists + id: guard + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + EXISTING=$(gh pr list --state open --json headRefName \ + --jq '[.[] | select(.headRefName | startswith("release/"))] | length') + if [ "$EXISTING" != "0" ]; then + echo "Open release PR already exists - a previous train is stuck. Skipping." | tee -a "$GITHUB_STEP_SUMMARY" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Bump internal @agentage/* dependencies to latest + id: deps + if: steps.guard.outputs.skip != 'true' + run: | + set -euo pipefail + # L2 upgrade: pull in memory-core + server-memory versions released + # earlier tonight. A dep change alone justifies the weekly release. + npx --yes npm-check-updates -u --dep prod,dev --filter "@agentage/*" + npm install + if git diff --quiet -- package.json package-lock.json; then + echo "No @agentage/* dependency changes." + echo "deps_changed=false" >> "$GITHUB_OUTPUT" + else + echo "Internal dependencies bumped." + git --no-pager diff -- package.json | grep -E '@agentage/' || true + echo "deps_changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Detect releasable commits since the last tag + id: detect + if: steps.guard.outputs.skip != 'true' + run: | + set -euo pipefail + # Anchor on the CURRENT release line: the repo carries stale v0.24.x + # tags from the pre-reboot CLI that sort above the live 0.x line, so + # semver-max would flag every week as releasable forever. Prefer the + # tag of the current package.json version; fall back to the nearest + # ancestor tag. + PKG_TAG="v$(node -p "require('./package.json').version")" + if git rev-parse -q --verify "refs/tags/${PKG_TAG}" >/dev/null; then + LAST_TAG="$PKG_TAG" + else + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true) + fi + RANGE=""; [ -n "$LAST_TAG" ] && RANGE="${LAST_TAG}..HEAD" + echo "Last tag: ${LAST_TAG:-none}" + SUBJECTS=$(git log $RANGE --no-merges --pretty=%s) + # Releasable = anything except docs/ci/plain-chore; chore(deps) counts + # (dependabot bundles ship weekly), chore(release) does not. + RELEASABLE=$(printf '%s\n' "$SUBJECTS" \ + | grep -vE '^(docs|ci)(\([^)]*\))?!?:' \ + | grep -vE '^chore(\([^)]*\))?!?:' || true) + DEPS=$(printf '%s\n' "$SUBJECTS" | grep -E '^chore\(deps' || true) + COUNT=$(printf '%s\n%s\n' "$RELEASABLE" "$DEPS" | grep -c . || true) + if [ "$COUNT" -eq 0 ]; then + echo "No releasable commits since ${LAST_TAG:-repo start}." + echo "commits=false" >> "$GITHUB_OUTPUT" + else + echo "Found $COUNT releasable commit(s)." + echo "commits=true" >> "$GITHUB_OUTPUT" + fi + + - name: Decide whether to release + id: decide + if: steps.guard.outputs.skip != 'true' + run: | + if [ "${{ steps.deps.outputs.deps_changed }}" = "true" ] || [ "${{ steps.detect.outputs.commits }}" = "true" ]; then + echo "release=true" >> "$GITHUB_OUTPUT" + else + echo "Neither dep changes nor releasable commits - no release this week." | tee -a "$GITHUB_STEP_SUMMARY" + echo "release=false" >> "$GITHUB_OUTPUT" + fi + + - name: Bump minor version + id: bump + if: steps.decide.outputs.release == 'true' + run: | + set -euo pipefail + OUT=$(node scripts/bump-version.js minor) + echo "$OUT" + NEW=$(echo "$OUT" | grep "NEW_VERSION=" | cut -d= -f2) + npm install --package-lock-only + echo "New version: $NEW" + echo "version=$NEW" >> "$GITHUB_OUTPUT" + + - name: Verify + if: steps.bump.outputs.version + run: npm run verify + + - name: Create + auto-merge release PR + id: pr + if: steps.bump.outputs.version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + VERSION="${{ steps.bump.outputs.version }}" + BRANCH="release/${VERSION}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + git add package.json package-lock.json + git commit -m "chore(release): ${VERSION}" + git push origin "$BRANCH" + PR_URL=$(gh pr create --base master --head "$BRANCH" \ + --title "chore(release): ${VERSION}" \ + --body "Weekly minor release train. Version ${VERSION} of @agentage/cli. Publish happens via publish.yml after merge.") + echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" + sleep 5 + # A GITHUB_TOKEN-created PR fires no PR checks, so --auto is rejected with + # "clean status" - fall back to an immediate squash merge. + if ! OUT=$(gh pr merge "$PR_URL" --auto --squash 2>&1); then + echo "$OUT" + echo "$OUT" | grep -q "clean status" && gh pr merge "$PR_URL" --squash || exit 1 + fi + + - name: Dispatch publish workflow + if: steps.pr.outputs.pr_url + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # A GITHUB_TOKEN merge never fires publish.yml's push trigger, so dispatch + # it explicitly. Its dispatch path re-verifies, skips if the version is + # already on npm, then publishes + tags. + gh workflow run publish.yml --ref master --repo "${{ github.repository }}" + echo "Released ${{ steps.bump.outputs.version }} - publish.yml dispatched." >> "$GITHUB_STEP_SUMMARY" From c70af62f30a23d3ac4e5d5fbe1031a81aa9d2627 Mon Sep 17 00:00:00 2001 From: Volodymyr Vreshch Date: Tue, 11 Aug 2026 01:41:20 +0200 Subject: [PATCH 4/5] ci: drop release-train.yml (renamed) --- .github/workflows/release-train.yml | 200 ---------------------------- 1 file changed, 200 deletions(-) delete mode 100644 .github/workflows/release-train.yml diff --git a/.github/workflows/release-train.yml b/.github/workflows/release-train.yml deleted file mode 100644 index 0d50c59..0000000 --- a/.github/workflows/release-train.yml +++ /dev/null @@ -1,200 +0,0 @@ -name: Release Train - -# Fully automated Friday MINOR release of @agentage/cli to npm. No human step on -# the happy path. Weekly cadence, always a minor bump. -# -# Level 2 in the @agentage dependency chain: this repo depends on -# @agentage/memory-core AND @agentage/server-memory, which release earlier the -# same evening. L0 runs 19:00 UTC, L1 runs 20:00 UTC, this L2 train runs 21:00 -# UTC so it picks up their freshly published versions. Single plain UTC cron - -# no Prague/DST gate: if GitHub fires the cron late, the train simply departs -# late instead of being gated away. -# -# GITHUB_TOKEN caveats handled here: its branch pushes don't fire pr-validation on -# the release PR (the in-workflow `npm run verify` is the CI gate), and its merges -# don't fire publish.yml's push trigger - so after merging, this workflow -# dispatches publish.yml explicitly. -on: - schedule: - - cron: '0 21 * * 5' - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - actions: write - -jobs: - dependabot-quiescence: - name: Wait for dependabot triage to finish - runs-on: ubuntu-latest - timeout-minutes: 50 - steps: - - name: Wait until this repo has no fresh open dependabot PRs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - run: | - set -euo pipefail - # Dependabot merging is owned by the org triage automation; this job only - # delays the train while that agent may still be working. PRs still open - # after the wait were deliberately left by triage - the train departs - # without them. - for i in $(seq 1 45); do - OPEN=$(gh pr list -R "$REPO" --author "app/dependabot" --state open --json number --jq 'length') - [ "$OPEN" = "0" ] && { echo "No open dependabot PRs - proceeding."; exit 0; } - echo "poll $i: $OPEN open dependabot PR(s), waiting for triage..." - sleep 60 - done - echo "Dependabot PR(s) still open after wait - releasing without them." | tee -a "$GITHUB_STEP_SUMMARY" - - release: - name: Cut minor release - needs: dependabot-quiescence - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Checkout code - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v7 - with: - node-version: '22' - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Skip if an open release PR exists - id: guard - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - EXISTING=$(gh pr list --state open --json headRefName \ - --jq '[.[] | select(.headRefName | startswith("release/"))] | length') - if [ "$EXISTING" != "0" ]; then - echo "Open release PR already exists - a previous train is stuck. Skipping." | tee -a "$GITHUB_STEP_SUMMARY" - echo "skip=true" >> "$GITHUB_OUTPUT" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Bump internal @agentage/* dependencies to latest - id: deps - if: steps.guard.outputs.skip != 'true' - run: | - set -euo pipefail - # L2 upgrade: pull in memory-core + server-memory versions released - # earlier tonight. A dep change alone justifies the weekly release. - npx --yes npm-check-updates -u --dep prod,dev --filter "@agentage/*" - npm install - if git diff --quiet -- package.json package-lock.json; then - echo "No @agentage/* dependency changes." - echo "deps_changed=false" >> "$GITHUB_OUTPUT" - else - echo "Internal dependencies bumped." - git --no-pager diff -- package.json | grep -E '@agentage/' || true - echo "deps_changed=true" >> "$GITHUB_OUTPUT" - fi - - - name: Detect releasable commits since the last tag - id: detect - if: steps.guard.outputs.skip != 'true' - run: | - set -euo pipefail - # Anchor on the CURRENT release line: the repo carries stale v0.24.x - # tags from the pre-reboot CLI that sort above the live 0.x line, so - # semver-max would flag every week as releasable forever. Prefer the - # tag of the current package.json version; fall back to the nearest - # ancestor tag. - PKG_TAG="v$(node -p "require('./package.json').version")" - if git rev-parse -q --verify "refs/tags/${PKG_TAG}" >/dev/null; then - LAST_TAG="$PKG_TAG" - else - LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true) - fi - RANGE=""; [ -n "$LAST_TAG" ] && RANGE="${LAST_TAG}..HEAD" - echo "Last tag: ${LAST_TAG:-none}" - SUBJECTS=$(git log $RANGE --no-merges --pretty=%s) - # Releasable = anything except docs/ci/plain-chore; chore(deps) counts - # (dependabot bundles ship weekly), chore(release) does not. - RELEASABLE=$(printf '%s\n' "$SUBJECTS" \ - | grep -vE '^(docs|ci)(\([^)]*\))?!?:' \ - | grep -vE '^chore(\([^)]*\))?!?:' || true) - DEPS=$(printf '%s\n' "$SUBJECTS" | grep -E '^chore\(deps' || true) - COUNT=$(printf '%s\n%s\n' "$RELEASABLE" "$DEPS" | grep -c . || true) - if [ "$COUNT" -eq 0 ]; then - echo "No releasable commits since ${LAST_TAG:-repo start}." - echo "commits=false" >> "$GITHUB_OUTPUT" - else - echo "Found $COUNT releasable commit(s)." - echo "commits=true" >> "$GITHUB_OUTPUT" - fi - - - name: Decide whether to release - id: decide - if: steps.guard.outputs.skip != 'true' - run: | - if [ "${{ steps.deps.outputs.deps_changed }}" = "true" ] || [ "${{ steps.detect.outputs.commits }}" = "true" ]; then - echo "release=true" >> "$GITHUB_OUTPUT" - else - echo "Neither dep changes nor releasable commits - no release this week." | tee -a "$GITHUB_STEP_SUMMARY" - echo "release=false" >> "$GITHUB_OUTPUT" - fi - - - name: Bump minor version - id: bump - if: steps.decide.outputs.release == 'true' - run: | - set -euo pipefail - OUT=$(node scripts/bump-version.js minor) - echo "$OUT" - NEW=$(echo "$OUT" | grep "NEW_VERSION=" | cut -d= -f2) - npm install --package-lock-only - echo "New version: $NEW" - echo "version=$NEW" >> "$GITHUB_OUTPUT" - - - name: Verify - if: steps.bump.outputs.version - run: npm run verify - - - name: Create + auto-merge release PR - id: pr - if: steps.bump.outputs.version - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - VERSION="${{ steps.bump.outputs.version }}" - BRANCH="release/${VERSION}" - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git checkout -b "$BRANCH" - git add package.json package-lock.json - git commit -m "chore(release): ${VERSION}" - git push origin "$BRANCH" - PR_URL=$(gh pr create --base master --head "$BRANCH" \ - --title "chore(release): ${VERSION}" \ - --body "Weekly minor release train. Version ${VERSION} of @agentage/cli. Publish happens via publish.yml after merge.") - echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT" - sleep 5 - # A GITHUB_TOKEN-created PR fires no PR checks, so --auto is rejected with - # "clean status" - fall back to an immediate squash merge. - if ! OUT=$(gh pr merge "$PR_URL" --auto --squash 2>&1); then - echo "$OUT" - echo "$OUT" | grep -q "clean status" && gh pr merge "$PR_URL" --squash || exit 1 - fi - - - name: Dispatch publish workflow - if: steps.pr.outputs.pr_url - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # A GITHUB_TOKEN merge never fires publish.yml's push trigger, so dispatch - # it explicitly. Its dispatch path re-verifies, skips if the version is - # already on npm, then publishes + tags. - gh workflow run publish.yml --ref master --repo "${{ github.repository }}" - echo "Released ${{ steps.bump.outputs.version }} - publish.yml dispatched." >> "$GITHUB_STEP_SUMMARY" From 8cb7e7fffc218ffd8a781e6eeba882cd1a8ef6b1 Mon Sep 17 00:00:00 2001 From: Volodymyr Vreshch Date: Tue, 11 Aug 2026 01:41:24 +0200 Subject: [PATCH 5/5] ci: dispatch the renamed workflow from train.yml --- .github/workflows/train.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/train.yml b/.github/workflows/train.yml index 0d50c59..84282ec 100644 --- a/.github/workflows/train.yml +++ b/.github/workflows/train.yml @@ -196,5 +196,5 @@ jobs: # A GITHUB_TOKEN merge never fires publish.yml's push trigger, so dispatch # it explicitly. Its dispatch path re-verifies, skips if the version is # already on npm, then publishes + tags. - gh workflow run publish.yml --ref master --repo "${{ github.repository }}" + gh workflow run release.yml --ref master --repo "${{ github.repository }}" echo "Released ${{ steps.bump.outputs.version }} - publish.yml dispatched." >> "$GITHUB_STEP_SUMMARY"