diff --git a/.github/PULL_REQUEST_TEMPLATE/experimental.md b/.github/PULL_REQUEST_TEMPLATE/experimental.md new file mode 100644 index 000000000..e5491193a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/experimental.md @@ -0,0 +1,106 @@ + + +## πŸ§ͺ Experiment Overview + +**Feature Name:** `` (from `experimental/` branch) + +**Goal:** + + +**Success Criteria:** + + +**Affected Packages:** + +- [ ] `@accelint/` + +--- + +## πŸ“‹ Experiment Plan + +**Week 1:** + + +**Week 2:** + + +**Week 3:** + + +**Week 4:** + + +--- + +## πŸ” Findings & Feedback + + + +### What's Working + + +### What's Not Working + + +### Open Questions + + +--- + +## πŸ“¦ Installation + +Once published, stakeholders can install with: + +```bash +npm install @accelint/@ +``` + +Publish experimental snapshots via **Actions β†’ Publish Experimental Snapshot β†’ Run workflow**. + +--- + +## ⚠️ Experimental Status + +- **Quality Bar:** Minimal (build + types + lint only) +- **Tests:** Not required for experimental +- **Docs:** Not required for experimental +- **Time-Box:** 1 month maximum +- **Never Merges:** This PR will not merge to main + +**Age Tracking:** Automated workflow runs daily with escalating warnings at 2, 3, and 4 weeks. + +--- + +## 🎯 Final Decision + + + +### Decision: [Promote / Abandon] + +**If Promoting:** +- [ ] Close this experimental PR +- [ ] Delete experimental branch +- [ ] Open fresh PR to main with full implementation (tests + docs + review) +- [ ] Reference this PR in new PR for context + +**If Abandoning:** +- [ ] Document lessons learned below +- [ ] Close this experimental PR +- [ ] Delete experimental branch + +**Lessons Learned:** + + +--- + +πŸ“š **Documentation:** [Experimental Release Workflow](../docs/EXPERIMENTAL_RELEASE.md) diff --git a/.github/workflows/experimental-age-tracker.yml b/.github/workflows/experimental-age-tracker.yml new file mode 100644 index 000000000..b22539cb1 --- /dev/null +++ b/.github/workflows/experimental-age-tracker.yml @@ -0,0 +1,161 @@ +name: Experimental Age Tracker +on: + schedule: + # Run daily at 9 AM UTC + - cron: '0 9 * * *' + workflow_dispatch: +jobs: + track-age: + name: Track Experimental Branch Age + runs-on: ubuntu-latest + permissions: + pull-requests: write # to create/update PR comments + steps: + - uses: actions/checkout@v5 + + - name: Track experimental PR ages + uses: actions/github-script@v7 + with: + script: | + // Find all open PRs from experimental/* branches + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + }); + + const experimentalPRs = prs.filter(pr => + pr.head.ref.startsWith('experimental/') + ); + + console.log(`Found ${experimentalPRs.length} experimental PRs`); + + if (experimentalPRs.length === 0) { + console.log('No experimental PRs to track'); + return; + } + + // Process each experimental PR + for (const pr of experimentalPRs) { + console.log(`\nProcessing PR #${pr.number}: ${pr.title}`); + + try { + // Query workflow runs to find first successful publish + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'publish-experimental.yml', + branch: pr.head.ref, + status: 'success', + per_page: 100 + }); + + // Calculate age from first successful publish + let ageInDays, publishDate, dateStr; + + // Calculate deadline and remaining time + const currentDate = new Date(); + + if (runs.workflow_runs.length === 0) { + console.log(` ⚠️ No successful publish runs found.`); + ageInDays = 'N/A'; + dateStr = 'Not published'; + } else { + const firstRun = runs.workflow_runs + .sort((a, b) => new Date(a.created_at) - new Date(b.created_at))[0]; + + publishDate = new Date(firstRun.created_at); + dateStr = publishDate.toISOString().split('T')[0]; + ageInDays = Math.floor((currentDate - publishDate) / (1000 * 60 * 60 * 24)); + + console.log(` πŸ“… First published: ${dateStr}`); + console.log(` ⏱️ Age: ${ageInDays} days`); + } + + const deadline = publishDate ? new Date(publishDate.getTime() + 28 * 24 * 60 * 60 * 1000) : null; + const daysRemaining = deadline ? Math.floor((deadline - currentDate) / (1000 * 60 * 60 * 24)) : 0; + + // Determine status based on age + let emoji, status, nextSteps; + + if (ageInDays === 'N/A') { + emoji = '⚠️'; + status = 'Unpublished'; + nextSteps = `Once you publish, you will have a month for experimentation. Do not use your experiment in a production environment.`; + } else if (ageInDays > 27) { + emoji = '🚨'; + status = 'Overdue! - time to make a decision'; + nextSteps = `This experiment has exceeded the 1-month time-box.\n\n**Required actions:**\n1. **Promote:** Close this PR, reimplement with full quality bar (tests, docs, review), open fresh PR to main\n2. **Abandon:** Close this PR, delete experimental branch, document why`; + } else if (ageInDays >= 20) { + emoji = '⏰'; + status = 'Approaching deadline'; + nextSteps = `Deadline approaching in **${daysRemaining} days**.\n\n- Begin decision process: promote or abandon?\n- If promoting, plan full implementation with tests and docs\n- If abandoning, document lessons learned`; + } else if (ageInDays >= 13) { + emoji = '⏰'; + status = 'Active'; + nextSteps = `You are two weeks in! You have **${daysRemaining} days remaining** to evaluate this experiment.`; + } else { + emoji = 'ℹ️'; + status = 'Active'; + nextSteps = `You have **${daysRemaining} days remaining** to evaluate this experiment.\n\n- Test the experimental build\n- Gather feedback from users\n- Document findings`; + } + + console.log(` ${emoji} Status: ${status}`); + + // Fetch comments for status tracking + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number + }); + + // Generate status comment + const statusCommentBody = [ + '## πŸ“Š Experiment Status', + '', + `**Status:** ${emoji} ${status}`, + `**Published:** ${dateStr}`, + `**Age In Days:** ${ageInDays}`, + `**Deadline:** ${deadline ? deadline.toISOString().split('T')[0] : 'Countdown clock not started.'}`, + '', + '### Next Steps', + nextSteps, + '', + '---', + `*Last updated: ${currentDate.toUTCString()}*` + ].join('\n'); + + // Find existing status comment + const statusComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('πŸ“Š Experiment Status') + ); + + if (statusComment) { + // Update existing status comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: statusComment.id, + body: statusCommentBody + }); + console.log(` βœ“ Updated status comment`); + } else { + // Create new status comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: statusCommentBody + }); + console.log(` βœ“ Created status comment`); + } + + } catch (error) { + console.error(` ❌ Error processing PR #${pr.number}: ${error.message}`); + // Continue to next PR + } + } + + console.log('\nβœ“ Age tracking complete'); diff --git a/.github/workflows/publish-experimental.yml b/.github/workflows/publish-experimental.yml new file mode 100644 index 000000000..7dfc988d4 --- /dev/null +++ b/.github/workflows/publish-experimental.yml @@ -0,0 +1,195 @@ +name: Publish Experimental Snapshot +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Dry-run mode (skip npm publish, show what would happen)' + type: boolean + default: true +env: + TURBO_TELEMETRY_DISABLED: 1 +jobs: + publish-experimental: + name: Publish Experimental Snapshot + # Only allow on experimental/* branches + if: startsWith(github.ref, 'refs/heads/experimental/') + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write # to create/update PR comments + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + # Show run mode + - name: Check run mode + run: | + if [ "${{ inputs.dry_run }}" == "true" ]; then + echo "πŸ§ͺ DRY-RUN MODE: Will simulate publish without uploading to npm" + echo "## πŸ§ͺ Dry-Run Mode Enabled" >> $GITHUB_STEP_SUMMARY + echo "This workflow will show what would be published without actually publishing to npm." >> $GITHUB_STEP_SUMMARY + else + echo "πŸš€ PRODUCTION MODE: Will publish to npm registry" + echo "## πŸš€ Production Mode" >> $GITHUB_STEP_SUMMARY + echo "This workflow will publish packages to npm registry." >> $GITHUB_STEP_SUMMARY + fi + + # Extract feature name from branch (experimental/ β†’ ) + - name: Extract feature name + id: feature + run: | + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + FEATURE_NAME="${BRANCH_NAME#experimental/}" + + # Validate feature name. It flows into shell args and becomes an npm + # dist-tag, so restrict to a conservative slug pattern. + if [[ ! "$FEATURE_NAME" =~ ^[a-z][a-z0-9-]*$ ]]; then + echo "Error: feature name '$FEATURE_NAME' must match ^[a-z][a-z0-9-]*$" + echo " (lowercase letters, digits, and hyphens; must start with a letter)" + exit 1 + fi + + + echo "name=$FEATURE_NAME" >> $GITHUB_OUTPUT + echo "βœ“ Feature name: $FEATURE_NAME" + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Experimental Configuration" >> $GITHUB_STEP_SUMMARY + echo "- **Branch:** \`$BRANCH_NAME\`" >> $GITHUB_STEP_SUMMARY + echo "- **Feature:** \`$FEATURE_NAME\`" >> $GITHUB_STEP_SUMMARY + echo "- **npm tag:** \`@$FEATURE_NAME\`" >> $GITHUB_STEP_SUMMARY + + - uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - uses: pnpm/action-setup@v4 + with: + run_install: false + + - uses: actions/setup-node@v6 + with: + registry-url: 'https://registry.npmjs.org' + node-version-file: 'package.json' + cache: 'pnpm' + + - run: pnpm install + + - name: Build all packages + run: pnpm run build + + - name: Lint + run: pnpm run lint + + - name: Creating .npmrc + run: | + cat < "$HOME/.npmrc" + //registry.npmjs.org/:_authToken=$NPM_TOKEN + EOF + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + # Snapshot versioning and publishing + - name: Version snapshot + id: version + run: | + pnpm changeset version --snapshot ${{ steps.feature.outputs.name }} + + # Capture the version that was set + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Publish snapshot + if: ${{ !inputs.dry_run }} + run: | + pnpm changeset publish --tag ${{ steps.feature.outputs.name }} + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Dry-run publish + if: ${{ inputs.dry_run }} + run: | + pnpm changeset publish --tag ${{ steps.feature.outputs.name }} --dry-run + + # Find associated PR + - name: Find PR for this branch + id: pr + uses: actions/github-script@v7 + with: + script: | + const branch = context.ref.replace('refs/heads/', ''); + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + head: `${context.repo.owner}:${branch}`, + state: 'open' + }); + if (prs.length > 0) { + return prs[0].number; + } + return null; + + # Create or update PR comment + - name: Create or update PR comment + if: steps.pr.outputs.result != 'null' && !inputs.dry_run + uses: actions/github-script@v7 + with: + script: | + const prNumber = ${{ steps.pr.outputs.result }}; + const featureName = '${{ steps.feature.outputs.name }}'; + const version = '${{ steps.version.outputs.version }}'; + const publishDate = new Date().toISOString().split('T')[0]; + + const commentBody = `## πŸ§ͺ Experimental Snapshot Published + + **Feature tag:** \`${featureName}\` + **Version:** \`${version}\` + **Published:** ${publishDate} + + ### Installation + Install any toolkit package using the experimental tag: + \`\`\`bash + npm install @accelint/design-toolkit@${featureName} + npm install @accelint/map-toolkit@${featureName} + \`\`\` + + ### ⚠️ Experimental Release Warning + This is a proof-of-concept release with a **2-week shelf life**. + - APIs may change without notice + - Feature may be abandoned + - Do NOT use in production + + --- + *Last published: ${new Date().toISOString()}*`; + + // Find existing comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('πŸ§ͺ Experimental Snapshot Published') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: commentBody + }); + } diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml new file mode 100644 index 000000000..4cd8361ca --- /dev/null +++ b/.github/workflows/release-beta.yml @@ -0,0 +1,168 @@ +name: Beta Release +on: + push: + branches: + - 'beta/**' + workflow_dispatch: + inputs: + dry_run: + description: 'Dry-run mode (skip npm publish, show what would happen)' + type: boolean + default: true +env: + TURBO_TELEMETRY_DISABLED: 1 +concurrency: ${{ github.workflow }}-${{ github.ref }} +jobs: + release-beta: + name: Beta Release + runs-on: ubuntu-latest + permissions: + contents: write # to create release (changesets/action) + pull-requests: write # to post issue comments (changesets/action) + issues: write # to create pull request (changesets/action) + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 2 + + # Show run mode, dry run will be removed once workflow is validated + - name: Check run mode + run: | + if [ "${{ inputs.dry_run }}" == "true" ]; then + echo "πŸ§ͺ DRY-RUN MODE: Will simulate publish without uploading to npm" + echo "## πŸ§ͺ Dry-Run Mode Enabled" >> $GITHUB_STEP_SUMMARY + echo "This workflow will show what would be published without actually publishing to npm." >> $GITHUB_STEP_SUMMARY + else + echo "πŸš€ PRODUCTION MODE: Will publish to npm registry" + echo "## πŸš€ Production Mode" >> $GITHUB_STEP_SUMMARY + echo "This workflow will publish packages to npm registry." >> $GITHUB_STEP_SUMMARY + fi + + # Verify prerelease mode before proceeding + - name: Verify beta prerelease mode + run: | + if [ ! -f .changeset/pre.json ]; then + echo "Error: .changeset/pre.json not found" + echo "Run 'pnpm beta:start' to enter beta prerelease mode" + exit 1 + fi + MODE=$(jq -r '.mode' .changeset/pre.json) + TAG=$(jq -r '.tag' .changeset/pre.json) + if [ "$MODE" != "pre" ] || [ "$TAG" != "beta" ]; then + echo "Error: Prerelease mode is '$MODE' with tag '$TAG', expected mode 'pre' with tag 'beta'" + exit 1 + fi + echo "βœ“ Beta prerelease mode verified" + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Prerelease Configuration" >> $GITHUB_STEP_SUMMARY + echo "- **Mode:** \`$MODE\`" >> $GITHUB_STEP_SUMMARY + echo "- **Tag:** \`$TAG\`" >> $GITHUB_STEP_SUMMARY + + - uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - uses: pnpm/action-setup@v4 + with: + run_install: false + + - uses: actions/setup-node@v6 + with: + registry-url: 'https://registry.npmjs.org' + node-version-file: 'package.json' + cache: 'pnpm' + + - run: pnpm install + + # Quality checks + - name: Build all packages + run: pnpm run build + + - name: Lint + run: pnpm run lint + + - name: Test + run: pnpm run test + + - name: Creating .npmrc + run: | + cat < "$HOME/.npmrc" + //registry.npmjs.org/:_authToken=$NPM_TOKEN + EOF + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - uses: changesets/action@v1 + id: changesets + if: ${{ !inputs.dry_run }} + with: + # Call changeset command directly (skips constellation-tracker for beta) + version: pnpm changeset version + publish: changeset:release-beta + commit: 'build: version beta packages' + title: 'Changeset Action: Version Beta Packages + Publish' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + # Dry-run alternative: show what would be published + - name: Dry-run publish + if: ${{ inputs.dry_run }} + run: | + echo "## πŸ“¦ Dry-Run Publish Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Show what would be versioned + echo "### Version Changes" >> $GITHUB_STEP_SUMMARY + pnpm changeset version --snapshot dry-run 2>&1 | tee version-output.txt || true + + # Parse and display version changes + if [ -f version-output.txt ]; then + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + cat version-output.txt >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + fi + + # Show what would be published (dry-run) + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Packages That Would Be Published" >> $GITHUB_STEP_SUMMARY + pnpm changeset publish --tag beta --dry-run 2>&1 | tee publish-output.txt + + # Parse packages from output + if [ -f publish-output.txt ]; then + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + cat publish-output.txt >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + fi + + # Extract package list for summary + echo "" >> $GITHUB_STEP_SUMMARY + echo "**npm dist-tag:** \`@beta\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Installation (if published):**" >> $GITHUB_STEP_SUMMARY + grep -E "πŸ¦‹ @accelint/" publish-output.txt | sed 's/πŸ¦‹ //' | while read pkg; do + PKG_NAME=$(echo $pkg | cut -d'@' -f1-2) + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "npm install ${PKG_NAME}@beta" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + done || true + + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "πŸ’‘ **To publish for real:** Run this workflow again with dry-run unchecked" >> $GITHUB_STEP_SUMMARY + + - name: Show published packages + if: steps.changesets.outputs.published == 'true' && !inputs.dry_run + run: | + echo "βœ“ Beta packages published successfully" + echo "Published packages: ${{ steps.changesets.outputs.publishedPackages }}" + + echo "" >> $GITHUB_STEP_SUMMARY + echo "## βœ… Published Successfully" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Packages published to npm with \`@beta\` tag:**" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.changesets.outputs.publishedPackages }}" >> $GITHUB_STEP_SUMMARY diff --git a/docs/BETA_RELEASE.md b/docs/BETA_RELEASE.md new file mode 100644 index 000000000..37e8f91dc --- /dev/null +++ b/docs/BETA_RELEASE.md @@ -0,0 +1,295 @@ +# Beta Release Workflow + +## Overview + +Beta releases are **pre-stable release candidates** on the path to becoming stable versions. They represent feature-complete code that needs production validation before being promoted to `@latest`. + +**Quality Bar:** Full (tests, docs, review, verification gates) +**Version Format:** `X.Y.Z-beta.N` (e.g., `9.11.0-beta.1`) +**npm Dist-Tag:** `@beta` +**Branch Pattern:** `beta/v.` + +## When to Use Beta + +Use beta releases when: +- You need to validate a release candidate in production-like environments +- Features are complete but need real-world testing before stable release +- You want early adopters to test breaking changes +- You need a coordinated pre-release across multiple packages in the monorepo + +**Do NOT use beta for:** +- API exploration or experiments (use experimental releases instead) +- Incomplete features or work-in-progress +- Code without full test coverage and documentation + +## TSC Runbook + +### Starting a Beta Series + +**Prerequisites:** +- Features are complete on `main` branch +- All tests pass, documentation complete +- TSC approval obtained + +**Steps:** + +1. **Create beta branch from main:** + ```bash + git checkout main + git pull origin main + git checkout -b beta/v10.0 + ``` + +2. **Enter prerelease mode:** + ```bash + pnpm beta:start + ``` + This creates `.changeset/pre.json` with mode "beta". + +3. **Verify prerelease mode:** + ```bash + pnpm beta:status + ``` + Should show `"mode": "pre"` and `"tag": "beta"`. + +4. **Commit and push:** + ```bash + git add .changeset/pre.json + git commit -m "chore: enter beta prerelease mode" + git push origin beta/v10.0 + ``` + +5. **GitHub Actions automatically:** + - Verifies prerelease mode + - Runs quality checks (`pnpm run build`) + - Creates version PR with beta versions + - Publishes to npm with `@beta` tag after PR merge + +### Iterating on Beta + +**Making Changes:** + +Beta branches accept direct PRs for beta-specific fixes, though main-first development is encouraged for features. + +```bash +# On beta branch +git checkout beta/v10.0 +# Make changes +git add . +git commit -m "fix: beta-specific issue" +git push origin beta/v10.0 +``` + +The workflow automatically: +- Increments beta suffix (e.g., `9.11.0-beta.1` β†’ `9.11.0-beta.2`) +- Only versions changed packages (independent versioning) +- Publishes updated packages with `@beta` tag + +**Syncing from Main:** + +If main branch has critical fixes, merge them into beta: + +```bash +git checkout beta/v10.0 +git merge main +git push origin beta/v10.0 +``` + +### Exiting Beta and Promoting to Stable + +**When to Exit:** +- Beta validation complete +- No critical issues remain +- Ready for stable release +- TSC approval obtained + +**Exit Sequence:** + +1. **On beta branch, exit prerelease mode:** + ```bash + git checkout beta/v10.0 + pnpm beta:exit + ``` + This sets `.changeset/pre.json` mode to "exit". + +2. **Commit the exit:** + ```bash + git add .changeset/pre.json + git commit -m "chore: exit beta prerelease mode" + git push origin beta/v10.0 + ``` + +3. **Wait for version PR to be created and merged:** + The beta workflow will create a final version PR that removes `-beta.N` suffixes. + +4. **After the version PR merges, merge beta branch to main:** + ```bash + git checkout main + git pull origin main + git merge beta/v10.0 + git push origin main + ``` + +5. **Main branch's release workflow publishes stable versions:** + The existing `.github/workflows/release.yml` automatically publishes packages with `@latest` tag. + +6. **Clean up:** + ```bash + git branch -d beta/v10.0 + git push origin --delete beta/v10.0 + ``` + +**IMPORTANT:** Stable versions are ALWAYS published from the `main` branch. Beta branch only publishes beta versions. + +## Version Behavior + +### Independent Package Versioning + +Each package maintains its own version. Only changed packages get beta versions. + +**Example:** +- Before: `@accelint/design-toolkit@9.11.0`, `@accelint/core@0.6.0` +- Change design-toolkit only +- After: `@accelint/design-toolkit@9.11.0-beta.1`, `@accelint/core@0.6.0` (unchanged) + +### Version Increments + +Changesets determines the bump type (major/minor/patch) based on changeset files: + +```bash +# Create a changeset on beta branch +pnpm changeset +# Follow prompts to document changes +``` + +Beta iterations increment the beta suffix: +- `9.11.0-beta.1` β†’ `9.11.0-beta.2` β†’ `9.11.0-beta.3` + +On beta exit, the `-beta.N` suffix is removed: +- `9.11.0-beta.3` β†’ `9.11.0` (when merged to main) + +## Installation + +**Install latest beta version:** +```bash +npm install @accelint/design-toolkit@beta +``` + +**Install specific beta version:** +```bash +npm install @accelint/design-toolkit@9.11.0-beta.2 +``` + +**Check available beta versions:** +```bash +npm view @accelint/design-toolkit versions --tag beta +``` + +## Dependencies + +Changesets automatically handles internal dependencies: +- If package A depends on package B +- Both get beta versions when B changes +- Version ranges updated automatically + +No manual coordination needed. + +## Beta Direct PRs + +Beta branches can receive direct pull requests for fixes discovered during beta testing: + +```bash +# Create fix branch from beta +git checkout beta/v10.0 +git checkout -b fix-beta-issue +# Make changes +git push origin fix-beta-issue +# Open PR against beta/v10.0 (not main) +``` + +Main-first development is still encouraged for features, but urgent beta fixes can land directly on the beta branch. + +## Workflow Details + +### GitHub Workflow: `.github/workflows/release-beta.yml` + +**Trigger:** Push to `beta/**` branches + +**Steps:** +1. Verify `.changeset/pre.json` exists with mode "beta" +2. Run quality checks: build, lint, and test +3. Version packages: `pnpm changeset version` (direct call, skips constellation-tracker) +4. Publish packages: `changeset:publish-beta` + +**Fails if:** +- `.changeset/pre.json` missing (run `pnpm beta:start`) +- Prerelease mode is not "beta" +- Build fails +- Tests fail + +### constellation-tracker Integration + +**Beta releases do NOT trigger constellation-tracker** because: +- Beta is a pre-release, not production-ready +- Catalog should only track stable `@latest` versions +- Prevents pre-release noise in dependency tracking + +The beta workflow calls `changeset version` directly instead of using the `changeset:version` npm script (which includes constellation-tracker). + +## Troubleshooting + +### "Error: .changeset/pre.json not found" + +Run `pnpm beta:start` on your beta branch to enter prerelease mode. + +### "Prerelease mode is 'exit', expected 'pre'" + +You've already exited beta mode. Either: +- Merge beta to main to complete promotion +- Re-enter beta mode with `pnpm beta:start` for more iterations + +### Beta versions not incrementing + +Ensure you have changeset files documenting changes: +```bash +pnpm changeset +``` + +### Wrong packages getting versioned + +Changesets only versions packages with: +- Changeset files describing changes +- Or dependencies on changed packages (automatic) + +Review your changeset files in `.changeset/`. + +## npm Dist-Tags + +Beta packages use the `@beta` dist-tag: + +```bash +# List all tags for a package +npm dist-tag ls @accelint/design-toolkit + +# Example output: +# latest: 9.10.0 +# beta: 9.11.0-beta.2 +``` + +The `@latest` tag always points to the most recent stable version. + +## Best Practices + +1. **Time-box beta cycles:** Target 1-2 weeks for validation +2. **Communicate clearly:** Announce beta releases to early adopters +3. **Document changes:** Use changeset files to explain what changed and why +4. **Monitor feedback:** Track issues and PRs against beta branch +5. **Test thoroughly:** Beta is the last gate before stable - validate in production-like environments +6. **Sync regularly:** Merge main into beta to stay current with critical fixes +7. **Exit decisively:** When validation complete, exit and merge to main promptly + +## See Also + +- [Experimental Release Workflow](./EXPERIMENTAL_RELEASE.md) - For API exploration +- [Changesets Documentation](https://github.com/changesets/changesets) - Version management +- [npm Dist-Tags](https://docs.npmjs.com/cli/v9/commands/npm-dist-tag) - Tag management diff --git a/docs/EXPERIMENTAL_RELEASE.md b/docs/EXPERIMENTAL_RELEASE.md new file mode 100644 index 000000000..efd9df338 --- /dev/null +++ b/docs/EXPERIMENTAL_RELEASE.md @@ -0,0 +1,356 @@ +# Experimental Release Workflow + +## Overview + +Experimental releases are **time-boxed API sketches** for rapid feedback and iteration. They are NOT part of the version lineage and NEVER merge to main. + +**Quality Bar:** Minimal (build + types + lint only) +**Version Format:** `X.Y.Z-experimental--` +**npm Dist-Tag:** `@` +**Branch Pattern:** `experimental/` +**Time-Box:** 1 month maximum + +## When to Use Experimental + +Use experimental releases when: +- Exploring new API designs or patterns +- Need rapid feedback without full implementation +- Want to validate concepts before investing in full quality bar +- Testing breaking changes or radical redesigns + +**Do NOT use experimental for:** +- Pre-stable release candidates (use beta instead) +- Production-ready code (target main directly) +- Long-term feature development (use feature branches + main) + +While these experiments will be available for import via npm, they should be used judiciously and only for serious exploration. If you simply need a quick change to an existing library, consider using a [patch](https://pnpm.io/cli/patch) instead. + +## Experimental Workflow + +### 1. Create Experimental Branch + +```bash +git checkout main +git pull origin main +git checkout -b experimental/ +``` + +Branch naming: `experimental/` (lowercase, hyphens). Note that in order for publication to work correctly, your branch must start with "experimental/". + +### 2. Implement Minimal API Sketch + +Focus on: +- βœ… Core API surface (types, interfaces, key functions) +- βœ… Enough implementation to be testable +- ❌ **NOT required:** Tests, documentation, full implementation + +**Quality Gates (must pass):** +- Build succeeds (`pnpm run build`) +- Types pass (no TypeScript errors) +- Lint passes (`pnpm run lint`) + +**Not Required:** +- Test coverage +- Documentation +- Code review + +### 3. Open Draft PR + +Open a draft PR against `main` for tracking only: + +```bash +git push origin experimental/ +# Open draft PR on GitHub +``` + +**IMPORTANT:** This PR will NEVER merge. It's for tracking and discussion only. + +### 4. Publish Experimental Snapshot + +**Manual trigger via GitHub Actions UI:** + +1. Go to Actions β†’ "Publish Experimental Snapshot" +2. Click "Run workflow" +3. Select your `experimental/` branch +4. Click "Run workflow" + +The workflow will: +- Extract feature name from branch (`experimental/` β†’ ``) +- Run quality checks (build + types + lint) +- Version packages with snapshot: `9.11.0-experimental--` +- Publish to npm with `@` tag +- Create/update PR comment with install instructions + +**PR Comment Example:** + +```markdown +## πŸ§ͺ Experimental Snapshot Published + +**Feature:** `` +**Published:** 2026-05-07 + +### Published Packages +- `@accelint/design-toolkit@9.11.0-experimental--20260507120000` +- `@accelint/design-foundation@3.2.0-experimental--20260507120000` + +### Installation +npm install @accelint/design-toolkit@ +npm install @accelint/design-foundation@ + +### ⚠️ Experimental Warning +This is an experimental build for testing and feedback only... +``` + +### 5. Gather Feedback + +Share the `@` tag with stakeholders: + +```bash +npm install @accelint/design-toolkit@ +``` + +**Age Tracking:** Automated workflow runs daily to track experiment age: +- Days 0-13: ℹ️ **Active** - Continue testing +- Days 14-20: ⏰ **Approaching deadline** - Begin decision process +- Days 21-27: ⚠️ **Urgent** - Make decision soon +- Days 28+: 🚨 **Overdue** - Must decide now + +### 6. Decision Time + +After validation (max 1 month), choose one: + +#### Option A: Promote (Experiment Succeeded) + +**DO NOT merge the experimental PR.** Instead: + +1. Close the experimental PR +2. Delete experimental branch +3. Reimplement with full quality bar on a fresh branch: + ```bash + git checkout main + git checkout -b feat/ + # Reimplement with tests + docs + review + ``` +4. Open fresh PR to main with full code review + +**Why reimplement?** Experimental code lacks tests, docs, and review. Production code requires all of these. + +#### Option B: Abandon (Experiment Failed) + +1. Close the experimental PR +2. Delete experimental branch +3. Document lessons learned in PR comments or team notes + +```bash +git branch -D experimental/ +git push origin --delete experimental/ +``` + +## Key Principles + +### Never Merge to Main + +Experimental branches NEVER merge to main. Successful experiments are reimplemented with full quality bar. + +**Why?** +- Maintains high standards for production code +- Prevents low-quality code from entering main +- Forces intentional decision about what to keep +- Allows fast iteration without review burden + +### Time-Box: 1 Month Maximum + +Experiments must conclude within 28 days: +- Forces decision-making +- Prevents abandoned experiments from lingering +- Maintains repository hygiene +- Creates urgency for feedback + +Automated age tracker warns at 2, 3, and 4 weeks. + +### No Changesets Required + +Experimental publishes use snapshot versioning (`changeset version --snapshot`): +- No `.changeset/*.md` files needed +- Version based on current main + timestamp +- Not part of package version history + +### Focused Experiments (1-3 Packages) + +Keep experiments focused: +- βœ… **Good:** `experimental/` affecting 2-3 UI packages +- ❌ **Bad:** `experimental/rewrite-everything` affecting 15 packages + +Large-scale changes should target main directly with proper planning. + +## Installation + +**Install latest experimental version:** +```bash +npm install @accelint/design-toolkit@ +``` + +**Check available experimental versions:** +```bash +npm view @accelint/design-toolkit versions | grep experimental- +``` + +## Version Format + +``` +-experimental-- + +Example: 9.11.0-experimental--20260507120000 +``` + +- `base-version`: Current main branch version +- `feature`: Extracted from branch name +- `timestamp`: Publication time (YYYYMMDDHHmmss) + +Each publish gets a unique timestamp, allowing multiple iterations. + +## npm Dist-Tags + +Experimental packages use feature-specific tags: + +```bash +# List all tags +npm dist-tag ls @accelint/design-toolkit + +# Example output: +# latest: 9.11.0 +# beta: 9.11.0-beta.2 +# : 9.11.0-experimental--20260507120000 +``` + +## Age Tracker Details + +**Workflow:** `.github/workflows/experimental-age-tracker.yml` + +**Schedule:** Daily at 9 AM UTC (also manual dispatch) + +**Process:** +1. Finds all open PRs from `experimental/*` branches +2. Extracts publish date from bot comment +3. Calculates age in days +4. Determines warning level +5. Creates/updates "πŸ“Š Experiment Status" comment + +**Status Comment Example:** + +```markdown +## πŸ“Š Experiment Status + +**Status:** ⏰ Approaching deadline +**Started:** 2026-05-07 +**Age:** 17 days +**Deadline:** 2026-06-04 + +### Next Steps +Deadline approaching in 11 days. + +- Begin decision process: promote or abandon? +- If promoting, plan full implementation with tests and docs +- If abandoning, document lessons learned + +--- +*Last updated: Wed, 24 May 2026 09:00:00 GMT* +``` + +## No RFC Required + +Experimental branches don't require issues or RFCs: +- Lightweight process for exploration +- Document intent in PR description (encouraged but not required) +- Larger experiments should explain goals for team visibility + +## No Concurrent Limits + +No hard limit on active experimental branches: +- Trust team judgment +- If >5 active, discuss priorities +- Expected to be rare (most work targets main) + +## Workflow Details + +### GitHub Workflow: `.github/workflows/publish-experimental.yml` + +**Trigger:** Manual workflow dispatch only + +**Branch Restriction:** Only runs on `experimental/*` branches + +**Steps:** +1. Verify branch matches `experimental/*` pattern +2. Extract feature name from branch +3. Run quality checks: `pnpm run build` +4. Version snapshot: `pnpm changeset version --snapshot ` +5. Publish snapshot: `pnpm changeset publish --tag ` +6. Parse published packages from changeset output +7. Find associated draft PR +8. Create/update PR comment with install instructions + +### constellation-tracker Integration + +Experimental releases do NOT trigger constellation-tracker: +- Experiments are temporary, not production-ready +- Catalog should only track stable versions +- Prevents noise from short-lived experiments + +## Troubleshooting + +### "Error: This workflow only runs on experimental/* branches" + +Ensure your branch name starts with `experimental/`: +```bash +git checkout -b experimental/my-feature +``` + +### "Build failed" during publish + +Fix the build errors first. Experimental still requires: +- Successful build +- No TypeScript errors +- Lint passing + +Tests and docs are optional, but the code must compile. + +### PR comment not appearing + +Check that: +- You opened a PR from the experimental branch +- The PR is open (not closed) +- GitHub Actions has PR write permissions + +Trigger the workflow manually to retry. + +### Cannot install experimental version + +Verify the tag exists: +```bash +npm dist-tag ls @accelint/design-toolkit +``` + +If missing, republish via GitHub Actions workflow. + +## Best Practices + +1. **Start small:** Focus on 1-3 packages for targeted feedback +2. **Document intent:** Explain what you're exploring in PR description +3. **Gather feedback early:** Publish within first week, don't wait +4. **Decide within time-box:** 1 month maximum, don't let it linger +5. **Communicate status:** Update PR with findings as you learn +6. **Close decisively:** Promote with full quality bar or abandon and document why +7. **No merge temptation:** Experimental branches never merge - resist shortcuts + +## Example Timeline + +**Week 1:** Create branch, implement minimal API, publish snapshot +**Week 2:** Gather feedback, iterate based on findings +**Week 3:** Decision checkpoint - trending toward success or abandon? +**Week 4:** Final decision - promote (reimplement) or abandon (document) + +## See Also + +- [Beta Release Workflow](./BETA_RELEASE.md) - For pre-stable release candidates +- [Changesets Snapshot Versioning](https://github.com/changesets/changesets/blob/main/docs/snapshot-releases.md) +- [npm Dist-Tags](https://docs.npmjs.com/cli/v9/commands/npm-dist-tag) diff --git a/openspec/changes/beta-experimental-releases/design.md b/openspec/changes/beta-experimental-releases/design.md new file mode 100644 index 000000000..c176fba35 --- /dev/null +++ b/openspec/changes/beta-experimental-releases/design.md @@ -0,0 +1,387 @@ +# Design: Beta and Experimental Release Workflows + +## Context + +Standard-toolkit is a monorepo with 20+ packages under the `@accelint/*` scope. Currently, only stable releases are supported through the main branch using changesets + GitHub Actions. The bikeshed repository contains a proven implementation of beta and experimental release workflows that needs adaptation for the monorepo context. + +**Current Release Infrastructure:** +- Changesets for version management +- Turbo for build orchestration across packages +- constellation-tracker for dependency tracking (only runs for stable releases) +- GitHub Actions workflow (`release.yml`) publishes on main branch +- Each package has independent version in stable releases + +**Key Constraint:** constellation-tracker should NOT run for beta or experimental releases (both are pre-releases, only stable releases should update the catalog). + +**Bikeshed Reference Implementation:** +- Single package (simpler than monorepo) +- Beta scripts: `beta:start`, `beta:exit`, `beta:status` +- Three workflows: `release-beta.yml`, `publish-experimental.yml`, `experimental-age-tracker.yml` +- Proven patterns for branch naming, versioning, and age tracking + +## Goals / Non-Goals + +**Goals:** +- Enable beta release workflow for pre-stable validation in production-like environments +- Enable experimental release workflow for time-boxed (1-month) API exploration +- Adapt bikeshed workflows for monorepo context (multiple packages) +- Maintain existing stable release workflow unchanged +- Provide Core controls for beta lifecycle (start/exit) +- Automate age tracking for experimental branches + +**Non-Goals:** +- Changing the stable release process or main branch workflow +- Supporting multiple concurrent beta series (only one at a time) +- Allowing experimental branches to merge to main (must reimplement) +- Creating new package management or build tooling (use existing turbo + changesets) +- Independent beta versions for each package based on changes + +## Decisions + +### Decision 1: Adapt Bikeshed Workflows with Monorepo-Specific Changes + +**Choice:** Use bikeshed's three workflows as templates, modify for monorepo. + +**Rationale:** +- Bikeshed implementation is proven and documented +- Workflows already handle changesets, age tracking, PR comments +- Adaptation points are clear: turbo cache, multiple package handling, `@accelint/*` scope + +**Alternatives Considered:** +- Build from scratch: Higher risk, more time, reinventing solved problems +- Use external tool (e.g., Lerna prerelease): Doesn't fit our changesets setup + +**Trade-off:** Some bikeshed patterns need adjustment (single package β†’ multiple packages), but saves significant development time. + +**Monorepo Adaptations Required:** +- Add turbo cache strategy to workflows (already in main release workflow) +- Quality checks use `pnpm run build` (runs turbo across all packages) +- PR comments list ALL published packages with install instructions +- Parse changeset output to capture which packages were published + +### Decision 2: Branch-Based Workflow for Beta + +**Choice:** Beta releases run on long-lived `beta/v.` branches, not tags or changesets alone. + +**Rationale:** +- Isolates beta work from main (allows parallel stable development) +- Makes promotion explicit (merge beta β†’ main) +- Industry standard pattern (React and Next.js use similar branch-based approaches) +- Enables direct PRs for beta-specific fixes + +**Alternatives Considered:** +- Tags only: No isolation, harder to iterate on beta +- Changesets prerelease mode on main: Pollutes main branch, risky + +**Trade-off:** Requires branch management and eventual merge coordination, but provides safety and clarity. + +**Implementation:** `beta/v1.0`, `beta/v2.0` pattern (major.minor, patch handled by beta.N iterations). + +### Decision 3: Independent Package Versioning in Beta + +**Choice:** Each package maintains its own version number in beta. Changesets determines which packages need beta versions based on changes. + +**Rationale:** +- Matches stable release strategy (each package has independent version) +- Only changed packages get new beta versions +- Consumers can upgrade specific packages independently +- Reduces noise for packages that haven't changed + +**Alternatives Considered:** +- Unified versioning (force all packages to same version): Doesn't match current workflow, creates unnecessary version bumps +- Manual version coordination: Error-prone, defeats purpose of changesets + +**Trade-off:** Dependencies between beta packages need careful testing, but changesets handles this automatically. + +**Example:** +- design-toolkit at `2.3.0` β†’ `2.3.0-beta.1` (if changed) +- map-toolkit at `5.1.0` β†’ `5.1.0-beta.1` (if changed) +- core at `1.2.5` β†’ stays at `1.2.5` (if unchanged) + +**Branch naming:** `beta/vX.Y` is organizational (e.g., "Q2 2026 release cycle"), not prescriptive of version numbers. + +### Decision 4: Experimental Branches Never Merge + +**Choice:** Experimental branches use snapshot publishing (`changeset version --snapshot`) and are NEVER merged to main. Successful experiments are reimplemented with full quality bar. + +**Rationale:** +- Prevents low-quality code from entering main +- Experimental is for API exploration, not production code +- Allows fast iteration without tests/docs/review burden +- 1-month time-box creates forcing function for decision + +**Alternatives Considered:** +- Allow merge with lower quality bar: Pollutes main, reduces trust +- Require full quality bar for experimental: Defeats purpose (fast feedback) + +**Trade-off:** Duplicate work if experiment succeeds, but this is intentionalβ€”experiments are sketches. + +**Implementation:** Draft PR for tracking only, closed without merge. Fresh PR with full review if promoted. + +### Decision 5: Manual Publish for Experimental + +**Choice:** Experimental uses `workflow_dispatch` (manual trigger), not automatic publish on push. + +**Rationale:** +- Reduces npm registry noise (contributors iterate before publishing) +- Gives control over when to publish (after basic testing) +- Prevents accidental publishes during rapid iteration + +**Alternatives Considered:** +- Auto-publish on push: Too noisy, wastes npm resources +- Require PR approval first: Too heavyweight for experiments + +**Trade-off:** Extra step for developers (must click "Run workflow"), but acceptable given experimental nature. + +### Decision 6: constellation-tracker Integration + +**Choice:** constellation-tracker does NOT run for beta or experimental releases. + +**Rationale:** +- Both beta and experimental are pre-releases, not stable/production +- Catalog should only track stable releases that users should depend on +- Prevents pre-release noise in dependency tracking +- Simpler workflow - same approach for both channels + +**Implementation:** +- Beta workflow: Call `changeset version` and `changeset publish` directly (skip the `changeset:version` script) +- Experimental workflow: Already uses `changeset version --snapshot` (bypasses script) + +**Trade-off:** constellation-tracker won't see beta releases, but this is acceptable since they're not production-ready. + +### Decision 7: npm Scripts for Beta Lifecycle + +**Choice:** Add three convenience scripts to root package.json: `beta:start`, `beta:exit`, `beta:status`. + +**Rationale:** +- Simplifies Core workflow (clear commands) +- Documents the beta process in code +- Matches bikeshed pattern (proven) +- Reduces risk of manual errors (e.g., wrong prerelease mode) + +**Implementation:** +```json +"beta:start": "pnpm changeset pre enter beta", +"beta:exit": "pnpm changeset pre exit", +"beta:status": "test -f .changeset/pre.json && cat .changeset/pre.json || echo 'Not in prerelease mode'" +``` + +### Decision 8: Age Tracking via Daily Cron + +**Choice:** `experimental-age-tracker.yml` runs daily (9 AM UTC) to check experimental PR age and update status comments. + +**Rationale:** +- Prevents abandoned experiments from lingering +- Creates visibility and urgency (warnings at 14/21/28+ days) +- Automates governance (no manual tracking needed) + +**Alternatives Considered:** +- Weekly tracking: Too infrequent, experiments linger too long +- Webhook-based (on push): Doesn't track inactivity + +**Trade-off:** Requires cron maintenance, but very low overhead once configured. + +**Implementation:** Parse publish date from PR comment, calculate age, update status comment with warning level. + +## Workflow Details + +### Beta Workflow (`release-beta.yml`) + +**Trigger:** Push to `beta/**` branches + +**Steps:** +1. Verify `.changeset/pre.json` exists and mode is "beta" (fail fast if not) +2. Setup: pnpm, Node, turbo cache +3. Install dependencies +4. Run quality checks: `pnpm run build` (turbo builds all packages) +5. Use changesets action with `changeset:version` and `changeset:release` +6. Publish with `@beta` tag + +**Key Adaptations from bikeshed:** +- Add turbo cache step (from main release workflow) +- Use `pnpm run build` for monorepo (not `tsc`) +- Final step notes which packages were published (changesets determines this) +- Each package gets its own version number with `-beta.N` suffix + +### Experimental Workflow (`publish-experimental.yml`) + +**Trigger:** Manual `workflow_dispatch` + +**Branch Restriction:** Only runs on `experimental/*` branches + +**Steps:** +1. Extract feature name from branch (e.g., `experimental/dark-mode` β†’ `dark-mode`) +2. Run quality checks: `pnpm run build` (fail fast if broken) +3. Version snapshot: `pnpm changeset version --snapshot ` +4. Publish snapshot: `pnpm changeset publish --tag ` +5. Find associated draft PR +6. Create/update PR comment with install instructions for ALL published packages + +**Key Adaptations from bikeshed:** +- Capture multiple package versions (not just one) +- PR comment lists all `@accelint/*` packages published +- Install instructions show per-package commands + +### Age Tracker Workflow (`experimental-age-tracker.yml`) + +**Trigger:** Daily cron (9 AM UTC) + manual dispatch + +**Steps:** +1. Find all open PRs from `experimental/*` branches +2. For each PR: + - Read publish date from bot comment + - Calculate age in days + - Determine warning level (info/warning/urgent/overdue) + - Create/update status comment + +**Warning Levels:** +- Days 0-13: ℹ️ Active +- Days 14-20: ⏰ Approaching deadline +- Days 21-27: ⚠️ Urgent +- Days 28+: 🚨 Overdue - decide now + +**Reusable from bikeshed** with package name updates (`@switzerb/bikeshed` β†’ `@accelint/*`). + +## Files to Create + +### Workflows +1. `.github/workflows/release-beta.yml` - Beta release automation +2. `.github/workflows/publish-experimental.yml` - Experimental publish automation +3. `.github/workflows/experimental-age-tracker.yml` - Age tracking automation + +### Documentation +4. `docs/BETA_RELEASE.md` - Beta workflow guide +5. `docs/EXPERIMENTAL_RELEASE.md` - Experimental workflow guide + +### Optional +6. `.github/PULL_REQUEST_TEMPLATE/experimental.md` - Template for experimental PRs + +### Scripts (modify) +7. Root `package.json` - Add `beta:start`, `beta:exit`, `beta:status` scripts + +## Risks / Trade-offs + +### Risk: Prerelease Mode Errors Hard to Recover + +Changesets prerelease mode is stateful (`.changeset/pre.json`). Mistakes are hard to fix. + +**Mitigation:** +- Beta runs on separate branch (main protected) +- Document recovery: delete beta branch and restart +- Verify prerelease mode in workflow (fail fast) +- Core controls start/exit (not automated) + +### Risk: Multiple Packages Complicate Experimental Snapshots + +Single experimental branch may publish 5-10 packages, overwhelming users. + +**Mitigation:** +- Document guidance: experiments should be focused (1-3 packages max) +- PR comment clearly lists ALL affected packages +- Encourage small, targeted experiments + +### Risk: Beta Branch Diverges from Main + +Long-running beta branches accumulate merge conflicts. + +**Mitigation:** +- Target beta duration: 1-2 weeks (time-boxed) +- Regular syncs from main to beta (developer responsibility) +- Core oversees beta progress and release +- Merge conflicts resolved during beta exit + +### Risk: Age Tracker Date Parsing Failures + +If comment format changes, age calculation breaks. + +**Mitigation:** +- Use ISO date format consistently +- Test tracker with manually created comments first +- Graceful fallback if date unparseable (skip that PR) + +### Risk: Consumer Confusion with Multiple Channels + +Users might not understand beta vs experimental distinction. + +**Mitigation:** +- Clear documentation with decision tree +- Different branch naming (`beta/` vs `experimental/`) +- Different version formats (`X.Y.Z-beta.N` vs `X.Y.Z-experimental-feature-timestamp`) +- Experimental draft PR template explains purpose + +## Beta Exit and Promotion to Stable + +**Sequence:** +1. On beta branch: `pnpm beta:exit` (exits prerelease mode, removes `-beta.N` suffix) +2. Commit `.changeset/pre.json` changes and push +3. Merge beta branch to main via PR +4. Main branch's existing `release.yml` workflow handles stable publish to npm with `@latest` tag + +**Key principle:** Beta branch only publishes beta versions. Stable versions are ALWAYS published from main branch using the existing release workflow. + +**Rationale:** +- Clear separation: beta branch = beta releases, main = stable releases +- Reuses existing stable release workflow (no duplication) +- Familiar process for team (main branch always has stable versions) + +## Migration Plan + +**Phase 1: Beta Implementation** +1. Create `release-beta.yml` workflow +2. Add beta scripts to root `package.json` +3. Create `docs/BETA_RELEASE.md` +4. Test on feature branch: create `beta/v10.0`, run through full cycle +5. Verify npm publish, version PR creation, beta exit and merge to main + +**Phase 2: Experimental Implementation** +1. Create `publish-experimental.yml` workflow +2. Create `experimental-age-tracker.yml` workflow +3. Create `docs/EXPERIMENTAL_RELEASE.md` +4. Test on feature branch: create `experimental/test`, publish, verify age tracker +5. Verify PR comments, age warnings, cleanup + +**Phase 3: Documentation & Rollout** +1. Update CONTRIBUTING.md with release channel overview +2. Update README.md with installation instructions +3. Core training session on beta lifecycle +4. Team training on experimental workflow + +**Rollback Strategy:** +- Workflows have no destructive side effects (can be deleted safely) +- Beta branches can be deleted without affecting main +- Experimental branches never merge (no rollback needed) +- Published npm versions stay on registry (but tagged, not default) + +## Additional Principles + +### Beta Branch PRs + +**Decision:** Beta branches accept direct PRs for beta-specific fixes. + +**Rationale:** Provides flexibility for urgent fixes discovered during beta testing without requiring changes to land on main first. Main-first development is still encouraged for features, but beta-specific bug fixes can be committed directly to the beta branch. + +### npm Dist-Tags + +**Decision:** Use `@beta` tag for beta releases, `@` tag for experimental releases. + +**Examples:** +- Beta: `npm install @accelint/design-toolkit@beta` +- Experimental: `npm install @accelint/design-toolkit@dark-mode` + +**Rationale:** Clear naming that matches user expectations and industry conventions. + +### Experimental Branch Requirements + +**Decision:** No issue or RFC required to start an experimental branch. + +**Rationale:** Keep barrier low to encourage exploration. Larger experiments should document intent for team visibility, but this is encouraged, not required. + +### Concurrent Experimental Limits + +**Decision:** No hard limit on concurrent experimental branches. + +**Rationale:** Trust team judgment and use social coordination. If more than 5 experiments are active simultaneously, team should discuss priorities, but no enforcement mechanism needed. Experimental branches are expected to be rare. + +--- + +**Design Status:** Ready for implementation. All key decisions documented with rationale. diff --git a/openspec/changes/beta-experimental-releases/proposal.md b/openspec/changes/beta-experimental-releases/proposal.md new file mode 100644 index 000000000..541e8f167 --- /dev/null +++ b/openspec/changes/beta-experimental-releases/proposal.md @@ -0,0 +1,309 @@ +# Proposal: Beta and Experimental Release Workflows + +## Overview + +Introduce formal release workflows for beta (pre-stable release candidates) and experimental (time-boxed API sketches) packages in the standard-toolkit monorepo. This creates two parallel release tracks alongside stable releases, each with distinct quality bars, versioning schemes, and workflows. + +## Motivation + +Currently, standard-toolkit only supports stable releases through the main branch. This creates friction for: + +- **Beta releases**: Teams need to validate release candidates in production before promoting to stable, but have no formal workflow for versioning, publishing, and tracking beta builds. +- **API exploration**: New features require full quality bar (tests, docs, reviews) before merging to main, slowing down experimentation and making it costly to abandon ideas that don't work out. +- **Monorepo coordination**: All 20+ packages in the @accelint scope must be versioned together, but there's no mechanism for coordinated pre-releases. + +Other successful monorepos (React, Next.js) provide experimental builds for early feedback and beta channels for stabilization. Standard-toolkit needs the same. + +## Requirements + +### Beta Releases + +**Definition**: Release Candidate builds on the path to stable. Part of the version lineage (v1.0.0-beta.1 β†’ v1.0.0). + +**Quality Bar**: +- Full test coverage +- Complete documentation +- TSC review and approval +- All verification gates pass (build, test, lint, format) + +**Versioning**: +- Format: `X.Y.Z-beta.N` (e.g., `2.3.0-beta.1`, `5.1.0-beta.2`) +- Only one active beta series at a time (e.g., beta/v1.0) +- Each package maintains its own version (only changed packages get new beta versions) + +**Workflow**: +1. TSC approves starting a beta series +2. Create `beta/vX.Y` branch from main +3. Automated workflow versions and publishes beta builds on push +4. Team validates in production, files issues/PRs against beta branch +5. When stable, TSC approves exit: run `pnpm beta:exit` on beta branch +6. Merge beta branch to main via PR +7. Main branch's existing release workflow publishes stable versions to npm + +**Lifecycle**: +- Branch naming: `beta/v.` (e.g., `beta/v1.0`) +- Status tracking: `beta:status` command shows if in prerelease mode +- Merge strategy: Fast-forward merge to main when promoting to stable + +### Experimental Releases + +**Definition**: Time-boxed (1 month) API sketch builds. NOT part of version lineage. Never merges to main. + +**Quality Bar**: +- Build succeeds +- Types pass +- Lint passes +- No tests, docs, or review required + +**Versioning**: +- Format: `X.Y.Z-experimental--` (e.g., `9.12.0-experimental-new-api-20260507`) +- Version signals "not production ready" +- Feature name embedded in version tag + +**Workflow**: +1. Create `experimental/` branch from main +2. Implement minimal API sketch +3. Manual publish via workflow dispatch +4. Create draft PR for tracking (never merged) +5. Automated workflow tracks age daily, warns at 2 weeks, escalates to urgent at 3 weeks, overdue at 1 month +6. If successful, reimplement properly on main with full quality bar +7. Branch deleted after reimplement or abandonment + +**Lifecycle**: +- Branch naming: `experimental/` (e.g., `experimental/new-api`) +- Age tracking: Daily cron job checks branch age, warnings at 14/21/28+ days +- Cleanup: Manual deletion after reimplement or abandon decision + +## User-Facing API + +### Beta Workflow Commands + +```bash +# Start a beta series (TSC only) +pnpm beta:start v1.0 + +# Check beta status +pnpm beta:status +# Output (if in prerelease mode): +# { +# "mode": "pre", +# "tag": "beta", +# "initialVersions": { ... }, +# "changesets": [ ... ] +# } +# Or: "Not in prerelease mode" + +# Exit beta and promote to stable (TSC only) +pnpm beta:exit +``` + +### Experimental Workflow + +```bash +# Create experimental branch (any contributor) +git checkout -b experimental/new-feature + +# Publish experimental build (manual dispatch via GitHub Actions UI) +# Workflow: publish-experimental.yml + +# Check age warnings (automated daily cron) +# Workflow: experimental-age-tracker.yml +``` + +### GitHub Workflows + +**New workflows**: +- `.github/workflows/release-beta.yml`: Triggered on push to `beta/**` branches +- `.github/workflows/publish-experimental.yml`: Manual dispatch for experimental publishes +- `.github/workflows/experimental-age-tracker.yml`: Daily cron to check experimental branch age + +**Existing workflows**: +- `.github/workflows/changeset-release.yml`: Unchanged (stable releases only) + +## Architecture + +### Key Design Decisions + +**Decision 1: Independent versioning for beta** + +Each package maintains its own version number in beta. Changesets determines which packages need new beta versions based on changes. + +**Why**: Matches stable release strategy (independent versions), only changed packages get new versions, reduces noise. + +**Trade-off**: Consumers need to know which packages changed, but this matches the current workflow and provides clearer upgrade signals. + +**Decision 2: Branch-based workflow for beta** + +Beta builds live on long-lived `beta/vX.Y` branches rather than using changesets alone. + +**Why**: Isolates beta work from main, allows parallel stable development, makes promotion explicit (merge to main), matches industry patterns (React, Next.js). + +**Trade-off**: Requires branch management and merge coordination, but this is acceptable for the quality bar. + +**Decision 3: No merge for experimental** + +Experimental branches never merge to main. Successful experiments are reimplemented with full quality bar. + +**Why**: Prevents low-quality code from entering main, allows faster iteration, maintains high standards for production code. + +**Trade-off**: Duplicate work if experiment succeeds, but this is intentional (experiments are sketches, not production code). + +**Decision 4: Automated age tracking for experimental** + +Daily cron job checks experimental branch age with escalating warnings: starts at 2 weeks (approaching deadline), urgent at 3 weeks, overdue at 1 month. + +**Why**: Prevents abandoned experiments from lingering, maintains repository hygiene, creates forcing function for decision-making. Early warnings give teams time to plan next steps. + +**Trade-off**: Requires automation setup, but low maintenance once configured. + +**Decision 5: Manual publish for experimental** + +Experimental builds use manual workflow dispatch rather than automatic publish on push. + +**Why**: Reduces npm noise, gives control over when to publish, prevents accidental publishes during iteration. + +**Trade-off**: Extra step for developers, but acceptable given experimental nature. + +## Integration with Existing Systems + +### Changesets + +**Beta releases**: +- Beta branches use changesets normally +- Workflow calls `changeset version` directly (not npm script) +- constellation-tracker does NOT run (beta is pre-release) +- Pre-release mode enabled: versions become `X.Y.Z-beta.N` + +**Experimental releases**: +- No changesets required +- Version based on current main version: `X.Y.Z-experimental--` +- constellation-tracker does NOT run (experimental is temporary) + +### Turbo + +- All verification gates (build, test, lint, format) run for both beta and experimental +- Beta requires all gates to pass +- Experimental only requires build, lint (tests optional) + +### GitHub Actions + +**New automation**: +- Beta publish workflow (on push to beta branches) +- Experimental publish workflow (manual dispatch) +- Experimental age tracker (daily cron) + +**Secrets required**: +- `NPM_TOKEN`: For publishing to npm (already exists) +- `GITHUB_TOKEN`: For PR operations (provided by GitHub) + +## Success Criteria + +### Functional Requirements + +- Beta workflow creates and publishes versioned pre-release builds +- Experimental workflow publishes tagged builds without changesets +- Age tracking warns about stale experimental branches +- Beta status command shows current beta state +- Beta exit merges to main and publishes stable versions + +### Non-Functional Requirements + +- Beta workflow completes within 5 minutes +- Experimental workflow completes within 3 minutes +- Age tracker runs daily without manual intervention +- No breaking changes to existing stable release workflow +- Documentation covers both workflows with examples + +### User Experience + +- Clear distinction between beta (high quality, pre-stable) and experimental (low quality, time-boxed) +- TSC controls beta lifecycle (start, exit) +- Any contributor can create experimental branches +- Automated warnings prevent abandoned experiments +- Version numbers clearly signal stability level + +## Risks & Mitigations + +### Risk: Confusion between beta and experimental + +Users might not understand the difference or use the wrong workflow. + +**Mitigation**: +- Clear documentation with decision tree (when to use which) +- Different branch naming conventions (`beta/` vs `experimental/`) +- Different version formats (`X.Y.Z-beta.N` vs `X.Y.Z-experimental-feature-timestamp`) +- Draft PR template for experimental branches explains purpose + +### Risk: Beta branches diverge from main + +Long-running beta branches could accumulate merge conflicts with main. + +**Mitigation**: +- Beta series are time-sensitive (target: no more than 2-4 weeks ideally) +- Regular merges from main to beta branch (developer responsibility) +- Beta exit includes merge conflict resolution + +### Risk: Experimental branches pile up + +Contributors might create many experimental branches and forget to clean them up. + +**Mitigation**: +- Automated age tracking with warnings +- Monthly review of open experimental branches +- Clear documentation on cleanup expectations +- Branch protection rules prevent accidental merges + +### Risk: npm registry pollution + +Publishing many beta and experimental versions could clutter the npm registry. + +**Mitigation**: +- Beta versions use standard pre-release tags (filtered by default in npm) +- Experimental versions use `-experimental-` prefix (clearly not production) +- Consider npm dist-tags for better organization +- Documentation on how to install specific versions + +### Risk: Breaking changes in beta + +Beta builds might introduce breaking changes that affect early adopters. + +**Mitigation**: +- Beta is still pre-1.0 or pre-next-major (semver allows breaking changes) +- Clear documentation that beta is "use at own risk" +- Changelog documents all changes between beta versions +- TSC reviews breaking changes before beta exit + +## Governance + +### Beta Branch Management + +- **Beta PRs:** Beta branches accept direct PRs for beta-specific fixes. Main-first development encouraged for features, but urgent beta fixes can be committed directly. +- **Dependencies:** Changesets automatically handles internal dependency updates when packages have interdependencies. +- **TSC Controls:** TSC approves beta start (`pnpm beta:start`) and beta exit (`pnpm beta:exit`). + +### Experimental Branch Management + +- **No RFC Required:** Experimental branches don't require issues or RFCs. Encouraged for larger experiments but not mandatory. +- **No Hard Limits:** No limit on concurrent experimental branches. Team should discuss if >5 active, but no enforcement. +- **Expected Rarity:** Experimental branches are expected to be rare - most work should target main directly. + +### npm Dist-Tags + +- **Beta:** Published with `@beta` tag (e.g., `npm install @accelint/design-toolkit@beta`) +- **Experimental:** Published with feature-specific tags (e.g., `npm install @accelint/design-toolkit@dark-mode`) + +## Next Steps + +1. Create detailed design document with implementation decisions +2. Write specifications for each workflow component +3. Break down implementation into tasks +4. Implement beta workflow first (higher priority) +5. Implement experimental workflow second +6. Document workflows with examples +7. Train team on new processes + +--- + +**Estimated complexity:** Medium-High +**Estimated effort:** 5-7 days +**Priority:** High (unblocks beta releases for production validation) diff --git a/openspec/changes/beta-experimental-releases/specs/age-tracking/spec.md b/openspec/changes/beta-experimental-releases/specs/age-tracking/spec.md new file mode 100644 index 000000000..c762ed028 --- /dev/null +++ b/openspec/changes/beta-experimental-releases/specs/age-tracking/spec.md @@ -0,0 +1,120 @@ +# Spec: Experimental Age Tracking + +## ADDED Requirements + +### Requirement: Daily age tracking execution +The system SHALL run age tracking workflow daily at 9 AM UTC via cron schedule. + +#### Scenario: Daily cron trigger +- **WHEN** clock reaches 9:00 AM UTC +- **THEN** age tracker workflow executes automatically + +#### Scenario: Manual trigger support +- **WHEN** user manually triggers age tracker workflow +- **THEN** workflow runs immediately regardless of schedule + +### Requirement: Experimental PR discovery +The age tracker SHALL find all open pull requests from `experimental/*` branches. + +#### Scenario: Multiple experimental PRs +- **WHEN** age tracker runs with 3 open experimental PRs +- **THEN** tracker processes all 3 PRs + +#### Scenario: No experimental PRs +- **WHEN** age tracker runs with no open experimental PRs +- **THEN** tracker completes successfully without errors + +### Requirement: Publish date extraction +The age tracker SHALL extract publish date from bot comment on PR. + +#### Scenario: Parse publish date +- **WHEN** PR has bot comment with "**Published:** 2026-05-07" +- **THEN** tracker extracts date as 2026-05-07 + +#### Scenario: Missing publish comment +- **WHEN** PR has no bot comment with publish date +- **THEN** tracker skips that PR (no age calculation) + +#### Scenario: Unparseable date format +- **WHEN** bot comment has invalid date format +- **THEN** tracker skips that PR gracefully without crashing + +### Requirement: Age calculation +The age tracker SHALL calculate experiment age in days from publish date. + +#### Scenario: Recent experiment +- **WHEN** experimental branch published 5 days ago +- **THEN** tracker calculates age as 5 days + +#### Scenario: Old experiment +- **WHEN** experimental branch published 30 days ago +- **THEN** tracker calculates age as 30 days + +### Requirement: Warning level determination +The age tracker SHALL assign warning levels based on experiment age. + +#### Scenario: Active phase (0-13 days) +- **WHEN** experiment age is 10 days +- **THEN** tracker assigns status "ℹ️ Active" with info emoji + +#### Scenario: Approaching deadline (14-20 days) +- **WHEN** experiment age is 17 days +- **THEN** tracker assigns status "⏰ Approaching deadline" with clock emoji + +#### Scenario: Urgent phase (21-27 days) +- **WHEN** experiment age is 24 days +- **THEN** tracker assigns status "⚠️ Urgent" with warning emoji + +#### Scenario: Overdue (28+ days) +- **WHEN** experiment age is 30 days +- **THEN** tracker assigns status "🚨 Overdue - decide now" with alert emoji + +### Requirement: Status comment creation +The age tracker SHALL create status comment on PR if none exists. + +#### Scenario: First status comment +- **WHEN** PR has no existing status comment from bot +- **THEN** tracker creates new comment with "## πŸ“Š Experiment Status" header + +### Requirement: Status comment updates +The age tracker SHALL update existing status comment with current age and warning level. + +#### Scenario: Update existing comment +- **WHEN** PR has existing "πŸ“Š Experiment Status" comment +- **THEN** tracker updates that comment with new age and warning level + +#### Scenario: Comment content structure +- **WHEN** tracker updates status comment +- **THEN** comment includes: status emoji, started date, age in days, deadline date, next steps guidance + +### Requirement: Next steps guidance +The status comment SHALL provide actionable next steps based on warning level. + +#### Scenario: Active phase guidance +- **WHEN** experiment is in active phase (0-13 days) +- **THEN** comment shows days remaining and encourages testing/feedback + +#### Scenario: Overdue guidance +- **WHEN** experiment is overdue (28+ days) +- **THEN** comment instructs to either open fresh PR to main or close experimental PR + +### Requirement: Deadline calculation +The age tracker SHALL calculate deadline as publish date + 28 days (1 month). + +#### Scenario: Deadline display +- **WHEN** experiment published on 2026-05-07 +- **THEN** status comment shows deadline as 2026-06-04 + +### Requirement: Graceful error handling +The age tracker SHALL continue processing remaining PRs if one PR fails. + +#### Scenario: Error on single PR +- **WHEN** tracker encounters error parsing one PR +- **THEN** tracker logs error and continues to next PR without crashing + +### Requirement: Update timestamp +The status comment SHALL include "Last updated" timestamp. + +#### Scenario: Update timestamp display +- **WHEN** tracker updates status comment +- **THEN** comment footer shows "Last updated: " diff --git a/openspec/changes/beta-experimental-releases/specs/beta-workflow/spec.md b/openspec/changes/beta-experimental-releases/specs/beta-workflow/spec.md new file mode 100644 index 000000000..0f4bfc604 --- /dev/null +++ b/openspec/changes/beta-experimental-releases/specs/beta-workflow/spec.md @@ -0,0 +1,94 @@ +# Spec: Beta Release Workflow + +## ADDED Requirements + +### Requirement: Beta branch creation +The system SHALL support creating beta release branches with the naming pattern `beta/v.`. + +#### Scenario: TSC starts beta cycle +- **WHEN** TSC runs `pnpm beta:start` on main branch +- **THEN** system enters changesets prerelease mode with tag "beta" + +#### Scenario: Beta branch pushed +- **WHEN** beta branch is pushed to origin +- **THEN** GitHub Actions workflow triggers on `beta/**` branch pattern + +### Requirement: Beta prerelease mode verification +The beta release workflow SHALL verify that `.changeset/pre.json` exists and is in beta prerelease mode before publishing. + +#### Scenario: Missing prerelease file +- **WHEN** beta workflow runs without `.changeset/pre.json` +- **THEN** workflow fails with error message "Error: .changeset/pre.json not found" + +#### Scenario: Wrong prerelease mode +- **WHEN** beta workflow runs with prerelease mode != "beta" +- **THEN** workflow fails with error message "Error: Prerelease mode is '', expected 'beta'" + +### Requirement: Independent package versioning +The system SHALL version only changed packages with beta suffix, maintaining each package's independent version. + +#### Scenario: Selective beta versioning +- **WHEN** design-toolkit changes and map-toolkit does not +- **THEN** design-toolkit gets version `2.3.0-beta.1` and map-toolkit stays at `5.1.0` + +#### Scenario: Beta version increment +- **WHEN** beta branch has changes and is pushed again +- **THEN** changed packages increment beta suffix (e.g., `2.3.0-beta.1` β†’ `2.3.0-beta.2`) + +### Requirement: Beta quality gates +The beta workflow SHALL run all verification gates before publishing. + +#### Scenario: Build verification +- **WHEN** beta workflow runs +- **THEN** system executes `pnpm run build` and fails if build errors occur + +#### Scenario: Quality gate failure +- **WHEN** any verification gate fails +- **THEN** workflow stops and does not publish packages + +### Requirement: Beta npm publishing +The system SHALL publish beta packages with `@beta` dist-tag. + +#### Scenario: Beta package publication +- **WHEN** beta workflow completes versioning +- **THEN** changed packages are published to npm with tag `@beta` + +#### Scenario: Beta installation +- **WHEN** user runs `npm install @accelint/design-toolkit@beta` +- **THEN** npm installs the latest beta version of that package + +### Requirement: Beta exit and promotion +The system SHALL support exiting beta prerelease mode and promoting to stable. + +#### Scenario: TSC exits beta +- **WHEN** TSC runs `pnpm beta:exit` on beta branch +- **THEN** system exits prerelease mode and removes `-beta.N` suffix from package versions + +#### Scenario: Beta merge to main +- **WHEN** beta branch is merged to main after exit +- **THEN** main branch's existing release workflow publishes stable versions with `@latest` tag + +### Requirement: Beta status checking +The system SHALL provide a command to check current prerelease status. + +#### Scenario: Check beta status in prerelease mode +- **WHEN** user runs `pnpm beta:status` while in prerelease mode +- **THEN** system outputs the contents of `.changeset/pre.json` + +#### Scenario: Check beta status not in prerelease mode +- **WHEN** user runs `pnpm beta:status` while NOT in prerelease mode +- **THEN** system outputs "Not in prerelease mode" + +### Requirement: Beta direct PRs +The beta workflow SHALL accept direct pull requests to beta branches for beta-specific fixes. + +#### Scenario: Beta-specific fix +- **WHEN** contributor opens PR directly against beta branch +- **THEN** PR is allowed and can be merged without requiring changes on main first + +### Requirement: constellation-tracker exclusion +The beta workflow SHALL NOT run constellation-tracker during versioning. + +#### Scenario: Beta versioning without tracker +- **WHEN** beta workflow calls `changeset version` +- **THEN** constellation-tracker does NOT execute (catalog not updated) diff --git a/openspec/changes/beta-experimental-releases/specs/experimental-workflow/spec.md b/openspec/changes/beta-experimental-releases/specs/experimental-workflow/spec.md new file mode 100644 index 000000000..aa623c820 --- /dev/null +++ b/openspec/changes/beta-experimental-releases/specs/experimental-workflow/spec.md @@ -0,0 +1,116 @@ +# Spec: Experimental Release Workflow + +## ADDED Requirements + +### Requirement: Experimental branch creation +The system SHALL support creating experimental branches with the naming pattern `experimental/`. + +#### Scenario: Contributor creates experimental branch +- **WHEN** contributor creates branch `experimental/dark-mode` +- **THEN** branch is valid for experimental workflow + +#### Scenario: Invalid branch name +- **WHEN** contributor creates branch not matching `experimental/*` pattern +- **THEN** experimental publish workflow does not trigger + +### Requirement: Manual publish trigger +The experimental workflow SHALL require manual workflow dispatch trigger. + +#### Scenario: Manual publish initiation +- **WHEN** contributor triggers "Publish Experimental Snapshot" workflow from GitHub Actions UI +- **THEN** workflow runs only on `experimental/*` branches + +#### Scenario: Automatic push blocked +- **WHEN** contributor pushes to experimental branch +- **THEN** workflow does NOT automatically publish (requires manual trigger) + +### Requirement: Feature name extraction +The system SHALL extract feature name from experimental branch for versioning. + +#### Scenario: Feature name from branch +- **WHEN** workflow runs on `experimental/dark-mode` branch +- **THEN** feature name is extracted as "dark-mode" + +### Requirement: Experimental quality gates +The experimental workflow SHALL run minimal quality checks before publishing. + +#### Scenario: Build verification +- **WHEN** experimental workflow runs +- **THEN** system executes `pnpm run build` and fails fast if build errors occur + +#### Scenario: No test requirement +- **WHEN** experimental workflow runs +- **THEN** tests are NOT required to pass (optional quality gate) + +### Requirement: Snapshot versioning +The system SHALL version experimental packages based on current main version with experimental prefix. + +#### Scenario: Experimental version generation +- **WHEN** experimental workflow runs with feature "dark-mode" and main is at `9.12.0` +- **THEN** packages are versioned as `9.12.0-experimental-dark-mode-` + +#### Scenario: Timestamp uniqueness +- **WHEN** experimental publish runs multiple times +- **THEN** each publish gets a unique timestamp in version + +### Requirement: No changesets required +The experimental workflow SHALL NOT require changeset files. + +#### Scenario: Publish without changesets +- **WHEN** experimental branch has no `.changeset/*.md` files +- **THEN** workflow successfully publishes using `changeset version --snapshot` + +### Requirement: Feature-specific npm tags +The system SHALL publish experimental packages with feature-specific dist-tags. + +#### Scenario: Experimental package publication +- **WHEN** experimental workflow completes versioning for feature "dark-mode" +- **THEN** changed packages are published to npm with tag `@dark-mode` + +#### Scenario: Experimental installation +- **WHEN** user runs `npm install @accelint/design-toolkit@dark-mode` +- **THEN** npm installs the latest experimental version for that feature + +### Requirement: Draft PR comment +The experimental workflow SHALL create or update a PR comment with install instructions. + +#### Scenario: First publish comment +- **WHEN** experimental workflow publishes for the first time +- **THEN** bot creates comment on associated draft PR with install instructions for all published packages + +#### Scenario: Subsequent publish update +- **WHEN** experimental workflow publishes again on same branch +- **THEN** bot updates existing comment with new version and timestamp + +#### Scenario: Multiple packages published +- **WHEN** experimental publish affects 3 packages +- **THEN** PR comment lists install instructions for all 3 packages + +### Requirement: Never merge to main +The experimental workflow SHALL enforce that experimental branches never merge to main. + +#### Scenario: Experimental promotion +- **WHEN** experimental feature succeeds +- **THEN** contributor MUST close experimental PR and open fresh PR to main with full quality bar + +#### Scenario: Experimental abandonment +- **WHEN** experimental feature fails validation +- **THEN** contributor closes draft PR and deletes experimental branch + +### Requirement: constellation-tracker exclusion +The experimental workflow SHALL NOT run constellation-tracker during versioning. + +#### Scenario: Experimental versioning without tracker +- **WHEN** experimental workflow calls `changeset version --snapshot` +- **THEN** constellation-tracker does NOT execute (catalog not updated) + +### Requirement: Time-boxed lifecycle +The experimental workflow SHALL support 1-month time-box for experimental branches. + +#### Scenario: Publish date tracking +- **WHEN** experimental workflow publishes +- **THEN** PR comment includes publish date for age calculation + +#### Scenario: One-month limit +- **WHEN** experimental branch reaches 28 days from first publish +- **THEN** age tracker marks as overdue and requires decision diff --git a/openspec/changes/beta-experimental-releases/tasks.md b/openspec/changes/beta-experimental-releases/tasks.md new file mode 100644 index 000000000..f6becd4af --- /dev/null +++ b/openspec/changes/beta-experimental-releases/tasks.md @@ -0,0 +1,193 @@ +# Implementation Tasks: Beta and Experimental Releases + +## 1. Beta Workflow - Core Setup + +**Deliverable:** Beta scripts functional, can enter/exit prerelease mode locally + +- [x] 1.1 Add beta lifecycle scripts to root package.json (`beta:start`, `beta:exit`, `beta:status`) +- [x] 1.2 Test `pnpm beta:start` creates `.changeset/pre.json` with mode "beta" +- [x] 1.3 Test `pnpm beta:status` outputs prerelease JSON when in beta mode +- [x] 1.4 Test `pnpm beta:exit` removes prerelease mode +- [x] 1.5 Test `pnpm beta:status` outputs "Not in prerelease mode" when not in beta + +**Test:** Manually run full beta lifecycle locally (start β†’ status β†’ exit) and verify output + +## 2. Beta Workflow - GitHub Actions + +**Deliverable:** Beta workflow publishes packages with @beta tag when pushed to beta branches + +- [x] 2.1 Create `.github/workflows/release-beta.yml` based on bikeshed template +- [x] 2.2 Configure workflow trigger on `beta/**` branch pattern +- [x] 2.3 Add turbo cache strategy step (copy from existing release.yml) +- [x] 2.4 Add prerelease mode verification step (check `.changeset/pre.json` exists and mode is "beta") +- [x] 2.5 Configure quality checks to run `pnpm run build` +- [x] 2.6 Configure changesets action to call `changeset version` directly (NOT npm script to skip constellation-tracker) +- [x] 2.7 Configure changesets action to call `changeset publish` with `@beta` tag +- [x] 2.8 Add final step showing which packages were published + +**Test:** Create test beta branch `beta/v10.0`, run `pnpm beta:start`, push, verify workflow runs and creates version PR + +## 3. Beta Workflow - End-to-End Validation + +**Deliverable:** Complete beta cycle works from start to stable promotion + +- [ ] 3.1 Create test beta branch and enter prerelease mode +- [ ] 3.2 Make changes to 1-2 test packages +- [ ] 3.3 Push to trigger workflow and verify version PR created with beta versions +- [ ] 3.4 Merge version PR and verify packages published to npm with `@beta` tag +- [ ] 3.5 Verify `npm install @accelint/@beta` installs beta version +- [ ] 3.6 Make additional changes and verify beta.2 versions created +- [ ] 3.7 Run `pnpm beta:exit` and verify prerelease mode removed +- [ ] 3.8 Merge beta branch to main and verify stable versions published by main workflow +- [ ] 3.9 Delete test beta branch + +**Test:** Full beta cycle from creation to stable promotion verified on npm registry + +## 4. Experimental Workflow - Publish Automation + +**Deliverable:** Manual trigger publishes experimental snapshots with feature-specific tags + +- [x] 4.1 Create `.github/workflows/publish-experimental.yml` based on bikeshed template +- [x] 4.2 Configure workflow with `workflow_dispatch` trigger (manual only) +- [x] 4.3 Add branch restriction to only run on `experimental/*` branches +- [x] 4.4 Add feature name extraction step (from `experimental/` β†’ ``) +- [x] 4.5 Add quality checks step running `pnpm run build` +- [x] 4.6 Add snapshot version step: `changeset version --snapshot ` +- [x] 4.7 Add snapshot publish step: `changeset publish --tag ` +- [x] 4.8 Capture which packages were published (parse changeset output) + +**Test:** Create test experimental branch, manually trigger workflow, verify snapshot version generated + +## 5. Experimental Workflow - PR Comments + +**Deliverable:** Workflow creates/updates PR comments with install instructions for all published packages + +- [x] 5.1 Add PR discovery step (find open PR for current experimental branch) +- [x] 5.2 Add step to check for existing "πŸ§ͺ Experimental Snapshot Published" comment +- [x] 5.3 Generate comment body with: package names, versions, publish date, install instructions +- [x] 5.4 List ALL published packages in comment (handle monorepo multi-package case) +- [x] 5.5 Add experimental warning text to comment +- [x] 5.6 Create new comment if none exists, update existing if found +- [x] 5.7 Include "Last published" timestamp in comment + +**Test:** Trigger experimental publish, verify PR comment created with correct format and all packages listed + +## 6. Experimental Workflow - End-to-End Validation + +**Deliverable:** Complete experimental flow works from publish to installation + +- [ ] 6.1 Create test experimental branch `experimental/test-feature` +- [ ] 6.2 Make changes to 1-2 test packages +- [ ] 6.3 Open draft PR against main +- [ ] 6.4 Manually trigger publish workflow from GitHub Actions UI +- [ ] 6.5 Verify workflow completes and packages published with feature tag +- [ ] 6.6 Verify PR comment created with install instructions +- [ ] 6.7 Verify `npm install @accelint/@test-feature` installs experimental version +- [ ] 6.8 Make additional changes, republish, verify comment updated with new version +- [ ] 6.9 Close PR and delete experimental branch + +**Test:** Full experimental cycle verified including npm installation with feature tag + +## 7. Age Tracking - Automation Setup + +**Deliverable:** Daily cron workflow tracks experimental PR age and creates status comments + +- [x] 7.1 Create `.github/workflows/experimental-age-tracker.yml` based on bikeshed template +- [x] 7.2 Configure cron trigger for daily 9 AM UTC +- [x] 7.3 Add manual `workflow_dispatch` trigger for testing +- [x] 7.4 Add step to find all open PRs from `experimental/*` branches +- [x] 7.5 Add logic to find "πŸ§ͺ Experimental Snapshot Published" comment +- [x] 7.6 Add date parsing to extract publish date from comment +- [x] 7.7 Add age calculation logic (current date - publish date in days) +- [x] 7.8 Add warning level assignment (0-13: Active, 14-20: Approaching, 21-27: Urgent, 28+: Overdue) +- [x] 7.9 Add deadline calculation (publish date + 28 days) + +**Test:** Manually trigger age tracker, verify it finds experimental PRs + +## 8. Age Tracking - Status Comments + +**Deliverable:** Age tracker creates/updates status comments with escalating warnings + +- [x] 8.1 Add logic to check for existing "πŸ“Š Experiment Status" comment +- [x] 8.2 Generate status comment body with: emoji, status, started date, age, deadline +- [x] 8.3 Add "Next Steps" section with guidance based on warning level +- [x] 8.4 Add different next steps for active (days remaining) vs overdue (must decide) +- [x] 8.5 Add "Last updated" timestamp to comment footer +- [x] 8.6 Create new status comment if none exists +- [x] 8.7 Update existing status comment if found +- [x] 8.8 Handle errors gracefully (skip PR if date unparseable, continue to next) + +**Test:** Manually create test experimental PR with backdated publish comment, trigger age tracker, verify status comment created with correct warning level + +## 9. Age Tracking - End-to-End Validation + +**Deliverable:** Age tracking works across full lifecycle with escalating warnings + +- [ ] 9.1 Create experimental PR with publish comment dated 10 days ago +- [ ] 9.2 Manually trigger age tracker, verify "ℹ️ Active" status +- [ ] 9.3 Update publish date to 16 days ago, trigger tracker, verify "⏰ Approaching deadline" +- [ ] 9.4 Update publish date to 24 days ago, trigger tracker, verify "⚠️ Urgent" +- [ ] 9.5 Update publish date to 30 days ago, trigger tracker, verify "🚨 Overdue" +- [ ] 9.6 Verify status comment updates (not creates new) on subsequent runs +- [ ] 9.7 Test with PR missing publish comment, verify tracker skips gracefully +- [ ] 9.8 Clean up test PRs + +**Test:** Age tracker correctly escalates warnings and handles edge cases + +## 10. Documentation + +**Deliverable:** Complete documentation for both workflows and TSC runbooks + +- [x] 10.1 Create `docs/BETA_RELEASE.md` adapted from bikeshed proposal +- [x] 10.2 Update BETA_RELEASE.md with monorepo-specific examples (@accelint/* packages) +- [x] 10.3 Add TSC runbook section to BETA_RELEASE.md (start, iterate, exit procedures) +- [x] 10.4 Add beta exit sequence documentation (exit on branch, merge to main, main publishes stable) +- [x] 10.5 Create `docs/EXPERIMENTAL_RELEASE.md` adapted from bikeshed proposal +- [x] 10.6 Update EXPERIMENTAL_RELEASE.md with 1-month time-box and warning schedule +- [x] 10.7 Add guidance for focused experiments (1-3 packages max) +- [x] 10.8 Document npm dist-tags (@beta and @) +- [x] 10.9 Optionally create `.github/PULL_REQUEST_TEMPLATE/experimental.md` template +- [ ] 10.10 Update CONTRIBUTING.md to reference new release channels (optional) + +**Test:** Documentation reviewed for accuracy and completeness + +## 11. Final Validation + +**Deliverable:** Both workflows tested end-to-end, main release workflow unaffected + +- [ ] 11.1 Run full beta cycle on clean test branch +- [ ] 11.2 Run full experimental cycle on clean test branch +- [ ] 11.3 Verify constellation-tracker does NOT run for beta or experimental +- [ ] 11.4 Verify main branch stable release workflow still works unchanged +- [ ] 11.5 Test beta direct PR workflow (make fix on beta branch, merge) +- [ ] 11.6 Verify independent package versioning works (only changed packages get beta versions) +- [ ] 11.7 Test age tracker cron runs automatically (wait 24 hours or check logs) +- [ ] 11.8 Verify npm registry shows correct tags (@beta, @feature-name, @latest) +- [ ] 11.9 Document any issues or edge cases discovered +- [ ] 11.10 Clean up all test branches and tags + +**Test:** All acceptance criteria from GitLab issue #111 verified + +## Parallelization Strategy + +**Phase 1 (Sequential):** Complete tasks 1-3 first +- Beta core setup and workflow must work before moving to experimental +- Establishes foundation and verifies changesets prerelease mode + +**Phase 2 (Parallel):** Tasks 4-6 and 7-9 can run in parallel +- **Track A:** Experimental publish workflow (4-6) +- **Track B:** Age tracking automation (7-9) +- These are independent - experimental publish doesn't depend on age tracking +- Can be implemented by different people simultaneously + +**Phase 3 (Sequential):** Tasks 10-11 after Phase 2 complete +- Documentation requires understanding both workflows +- Final validation requires all features implemented + +**Dependencies:** +- Task 1 β†’ Task 2 β†’ Task 3 (beta must work end-to-end first) +- Task 4 β†’ Task 5 β†’ Task 6 (experimental publish, then comments, then validate) +- Task 7 β†’ Task 8 β†’ Task 9 (age tracking core, then comments, then validate) +- Tasks 4-6 and 7-9 independent of each other +- Task 10 requires 3, 6, 9 complete (docs need working features) +- Task 11 requires all previous tasks (final validation) diff --git a/package.json b/package.json index 1ebddbd87..caefbb2a2 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,15 @@ "scripts": { "audit:docblocks": "pnpm zx scripts/audit-docblocks.mjs", "bench": "pnpm turbo run bench", + "beta:exit": "pnpm changeset pre exit", + "beta:start": "pnpm changeset pre enter beta", + "beta:status": "test -f .changeset/pre.json && cat .changeset/pre.json || echo 'Not in prerelease mode'", "build": "pnpm turbo run build", "build:design-toolkit": "pnpm turbo run build --filter=@accelint/design-toolkit", "build:map-toolkit": "pnpm turbo run build --filter=@accelint/map-toolkit", "changeset": "pnpm exec changeset", "changeset:release": "pnpm run build && pnpm changeset publish", + "changeset:release-beta": "pnpm changeset publish --tag beta", "changeset:version": "pnpm changeset version && pnpm run constellation-tracker", "clean": "pnpm run clean:dist && pnpm run clean:buildinfo && pnpm run clean:turbo && pnpm run clean:cov && pnpm run clean:deps", "clean:buildinfo": "pnpm exec find . -name 'tsconfig.tsbuildinfo' -delete",