Skip to content

chore(release): v0.59.0 (#591) #1316

chore(release): v0.59.0 (#591)

chore(release): v0.59.0 (#591) #1316

Workflow file for this run

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"