From 91194cdbfface961926d56c8aa5bb6692af1bd55 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 29 Apr 2026 08:02:59 -0400 Subject: [PATCH] chore(#2828): add canary release workflow (#2830) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(#2828): add canary release workflow (dev builds on push to main) Publishes get-shit-done-cc@canary and @gsd-build/sdk@canary on every push to main. Version format: {base}-canary.{N} where base strips any pre-release suffix from package.json (1.39.0-rc.4 → 1.39.0-canary.1). Sequential canary number is auto-detected from existing git tags so reruns never collide. Concurrency group cancels stale in-flight canary runs when commits land quickly. Mirrors the structure and steps of release.yml: same checkout pins, Node 24, npm-publish environment, build:sdk, tarball verification, dry-run publish gate, and publish verification with sleep 10. Closes #2828 Co-Authored-By: Claude Sonnet 4.6 * fix(#2828): address CodeRabbit review findings on canary.yml - cancel-in-progress: false — was true, allowing a newer push to cancel a run mid-publish (after tag push but before SDK publish), leaving a partial release state that's unrecoverable since npm versions are immutable - Guard tag/publish/verify steps with github.ref == 'refs/heads/main' so a manual workflow_dispatch from a feature branch (dry_run defaults false) cannot accidentally publish unmerged code under the shared canary dist-tag - Replace fixed sleep 10 with exponential backoff retry loop (delays: 5 10 20 30 45s); fixed sleep is flaky against normal npm CDN replication lag and a false failure forces a new canary number since the tag already exists Co-Authored-By: Claude Sonnet 4.6 * feat(plan-phase): expose --mvp flag in command frontmatter Adds --mvp to argument-hint and Flags doc. Workflow handler in next commit. * chore(#2828): remove push:main trigger from canary workflow Submission rate to main is too high to auto-publish a canary on every merge. Restrict the workflow to manual workflow_dispatch only. Closes #2828 --------- Co-authored-by: Claude Sonnet 4.6 --- .github/workflows/canary.yml | 144 +++++++++++++++++++++++++++++++++++ commands/gsd/plan-phase.md | 3 +- 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/canary.yml diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml new file mode 100644 index 000000000..a27d73ea8 --- /dev/null +++ b/.github/workflows/canary.yml @@ -0,0 +1,144 @@ +name: Canary + +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run (skip npm publish, tagging, and push)' + required: false + type: boolean + default: false + +concurrency: + group: canary + cancel-in-progress: false + +env: + NODE_VERSION: 24 + +jobs: + canary: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + id-token: write + environment: npm-publish + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ env.NODE_VERSION }} + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - name: Determine canary version + id: canary + run: | + # Strip any pre-release suffix from package.json version to get base (e.g. 1.39.0-rc.4 → 1.39.0) + RAW=$(node -p "require('./package.json').version") + BASE=$(echo "$RAW" | sed 's/-.*//') + # Find next sequential canary number from existing tags + N=1 + while git tag -l "v${BASE}-canary.${N}" | grep -q .; do + N=$((N + 1)) + done + CANARY_VERSION="${BASE}-canary.${N}" + echo "canary_version=$CANARY_VERSION" >> "$GITHUB_OUTPUT" + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Bump to canary version + env: + CANARY_VERSION: ${{ steps.canary.outputs.canary_version }} + run: | + npm version "$CANARY_VERSION" --no-git-tag-version + cd sdk && npm version "$CANARY_VERSION" --no-git-tag-version && cd .. + + - name: Install and test + run: | + npm ci + npm test + + - name: Build SDK dist for tarball + run: npm run build:sdk + + - name: Verify tarball ships sdk/dist/cli.js (bug #2647) + run: bash scripts/verify-tarball-sdk-dist.sh + + - name: Dry-run publish validation + run: | + npm publish --dry-run --tag canary + cd sdk && npm publish --dry-run --tag canary + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Tag and push + if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }} + env: + CANARY_VERSION: ${{ steps.canary.outputs.canary_version }} + run: | + git tag "v${CANARY_VERSION}" + git push origin "v${CANARY_VERSION}" + + - name: Publish to npm (canary) + if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }} + run: npm publish --provenance --access public --tag canary + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish SDK to npm (canary) + if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }} + run: cd sdk && npm publish --provenance --access public --tag canary + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Verify publish + if: ${{ github.ref == 'refs/heads/main' && !inputs.dry_run }} + env: + CANARY_VERSION: ${{ steps.canary.outputs.canary_version }} + run: | + PUBLISHED="NOT_FOUND" + SDK_PUBLISHED="NOT_FOUND" + for delay in 5 10 20 30 45; do + PUBLISHED=$(npm view get-shit-done-cc@"$CANARY_VERSION" version 2>/dev/null || echo "NOT_FOUND") + SDK_PUBLISHED=$(npm view @gsd-build/sdk@"$CANARY_VERSION" version 2>/dev/null || echo "NOT_FOUND") + if [ "$PUBLISHED" = "$CANARY_VERSION" ] && [ "$SDK_PUBLISHED" = "$CANARY_VERSION" ]; then + break + fi + echo "Not yet live (sleeping ${delay}s)..." + sleep "$delay" + done + if [ "$PUBLISHED" != "$CANARY_VERSION" ]; then + echo "::error::Published version verification failed. Expected $CANARY_VERSION, got $PUBLISHED" + exit 1 + fi + echo "Verified: get-shit-done-cc@$CANARY_VERSION is live on npm" + if [ "$SDK_PUBLISHED" != "$CANARY_VERSION" ]; then + echo "::error::SDK version verification failed. Expected $CANARY_VERSION, got $SDK_PUBLISHED" + exit 1 + fi + echo "Verified: @gsd-build/sdk@$CANARY_VERSION is live on npm" + CANARY_TAG=$(npm dist-tag ls get-shit-done-cc 2>/dev/null | grep "canary:" | awk '{print $2}') + echo "canary dist-tag points to: $CANARY_TAG" + + - name: Summary + env: + CANARY_VERSION: ${{ steps.canary.outputs.canary_version }} + DRY_RUN: ${{ inputs.dry_run }} + run: | + echo "## Canary v${CANARY_VERSION}" >> "$GITHUB_STEP_SUMMARY" + if [ "$DRY_RUN" = "true" ]; then + echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY" + else + echo "- Published to npm as \`canary\`" >> "$GITHUB_STEP_SUMMARY" + echo "- SDK also published: \`@gsd-build/sdk@${CANARY_VERSION}\` on \`canary\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Tagged \`v${CANARY_VERSION}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Install: \`npx get-shit-done-cc@canary\`" >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/commands/gsd/plan-phase.md b/commands/gsd/plan-phase.md index 11ab0feb6..e530e40ed 100644 --- a/commands/gsd/plan-phase.md +++ b/commands/gsd/plan-phase.md @@ -1,7 +1,7 @@ --- name: gsd:plan-phase description: Create detailed phase plan (PLAN.md) with verification loop -argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd ] [--reviews] [--text] [--tdd]" +argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd ] [--reviews] [--text] [--tdd] [--mvp]" agent: gsd-planner allowed-tools: - Read @@ -42,6 +42,7 @@ Phase number: $ARGUMENTS (optional — auto-detects next unplanned phase if omit - `--prd ` — Use a PRD/acceptance criteria file instead of discuss-phase. Parses requirements into CONTEXT.md automatically. Skips discuss-phase entirely. - `--reviews` — Replan incorporating cross-AI review feedback from REVIEWS.md (produced by `/gsd-review`) - `--text` — Use plain-text numbered lists instead of TUI menus (required for `/rc` remote sessions) +- `--mvp` — Vertical MVP mode. Planner organizes tasks as feature slices (UI→API→DB) instead of horizontal layers. On Phase 1 of a new project, also emits `SKELETON.md` (Walking Skeleton). Can be persisted on a phase via `**Mode:** mvp` in ROADMAP.md. Normalize phase input in step 2 before any directory lookups.