chore(release): v0.59.0 (#591) #1316
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| schedule: | |
| # Weekly on Sunday at midnight UTC - catches runner image and environmental drift | |
| - cron: '0 0 * * 0' | |
| # Cancel superseded PR runs; main pushes each get a unique group (run_id) so they queue | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | |
| cancel-in-progress: true | |
| jobs: | |
| test: | |
| name: test (node ${{ matrix.node-version }}) | |
| runs-on: ubuntu-latest | |
| # 30 (was 20): headroom for the growing suite + setup. The suite runs | |
| # parallel (vitest maxWorkers:3 on the 4-vCPU runner); sequential timed out. | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node-version: ['24'] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.25' | |
| cache: false # No go.mod in repo — Go is only here for the golangci-lint binary | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| cache: 'pip' | |
| cache-dependency-path: '.github/requirements-ci.txt' | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy, rustfmt | |
| # Pre-built binary (~3s) instead of go install (~55s compile from source) | |
| - name: Install golangci-lint v2 | |
| run: curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.10.1 | |
| - name: Install Python tools | |
| run: pip install -r .github/requirements-ci.txt | |
| - name: Install Poetry | |
| run: pipx install poetry | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build | |
| run: bun run --cwd packages/cli build | |
| - name: Test CLI | |
| run: bun run --cwd packages/cli test | |
| env: | |
| TEST_POETRY: '1' | |
| - name: Acceptance lane (cucumber) | |
| # Guards the Gherkin acceptance lane against features merged with no step | |
| # definitions and no @wip tag — a persistently-red lane masks real | |
| # regressions (issue #341). Runs after Build because some step defs spawn | |
| # the built CLI (dist/cli.js). Features whose proof lives in vitest are | |
| # @wip-excluded by convention, each with a one-line pointer in the .feature. | |
| run: bun run test:bdd | |
| - name: Release-gate tests (supply-chain + dogfood parity) | |
| # Runs the *.release.test.ts files excluded from the main suite. | |
| # Includes the no-install-scripts.release.test.ts guard which would | |
| # otherwise only fire at `bun publish` time via prepublishOnly — | |
| # bypassable when publishing pre-built tarballs. Wiring to CI makes | |
| # this defense per-PR and unbypassable. | |
| run: bun run --cwd packages/cli test:release | |
| lint: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| - name: Ticket closure rides ready PR | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false | |
| run: | | |
| gh pr diff "$PR_NUMBER" --name-only > /tmp/safeword-pr-changed-files.txt | |
| unset GH_TOKEN | |
| bun scripts/check-pr-ticket-done.ts --changed-files /tmp/safeword-pr-changed-files.txt | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Lockfile sync check | |
| # --frozen-lockfile catches resolved-version drift, but lets | |
| # cosmetic constraint-mirror drift slip through (e.g. package.json | |
| # pins `knip: 6.4.1` while bun.lock still has `^6.4.1`). | |
| # `bun install --lockfile-only` regenerates only the lockfile | |
| # (~17ms on synced tree, no node_modules writes); any diff means | |
| # the constraint mirror has drifted from package.json. | |
| run: | | |
| bun install --lockfile-only | |
| git diff --exit-code bun.lock || { | |
| echo "::error::bun.lock drifted from package.json. Run 'bun install' locally and commit." | |
| exit 1 | |
| } | |
| - name: Lint | |
| run: bun run lint | |
| - name: Format check | |
| # prettier --check . — guards against formatting drift that eslint | |
| # does not catch. Pre-commit (lint-staged) only formats staged files, | |
| # so drift could land on main undetected before this step existed. | |
| run: bun run format:check | |
| - name: Type check | |
| run: bun run --cwd packages/cli typecheck | |
| - name: Architecture check | |
| run: bun run deps | |
| - name: Architecture doc freshness (FPV0E4) | |
| # Hard backstop: fails the build if the generated architecture state doc | |
| # (.project/architecture.generated.md) is stale — i.e. the commit-time | |
| # auto-fix hook was bypassed or a doc was hand-committed out of band. | |
| # Honors the per-project opt-out (architectureDocEnforcement: false). | |
| run: bun packages/cli/src/cli.ts architecture --check | |
| - name: Duplicate ticket-ID guard | |
| # ticket 158: two ticket folders with the same `id:` frontmatter is | |
| # silent corruption — block merges if it ever lands. | |
| run: bun scripts/check-ticket-ids.ts | |
| - name: Version consistency check | |
| run: | | |
| CLI_VERSION=$(node -e "console.log(require('./packages/cli/package.json').version)") | |
| MARKETPLACE_VERSION=$(node -e "console.log(require('./.claude-plugin/marketplace.json').plugins[0].version)") | |
| if [ "$CLI_VERSION" != "$MARKETPLACE_VERSION" ]; then | |
| echo "::error::Version mismatch: CLI=$CLI_VERSION Marketplace=$MARKETPLACE_VERSION" | |
| exit 1 | |
| fi | |
| echo "Versions consistent: $CLI_VERSION" |