Files
matt wilkie c3e600c940 fix(pr-preflight): warn when a PR-gate workflow is red for every PR while the base shows green (#5251)
* fix(pr-preflight): sample PR-gate health, not just base-branch runs (bd-msll)

The base-branch health check reads gh run list --branch <base>, which only
sees workflows that run on the base branch itself. A job that exists only in
the PR workflow (pr.yml) never runs there, so preflight could report "base is
green" while every fresh PR was red in that job, and pr-babysit mirrors
preflight's verdict. Add a supplemental sample of recent completed
pull_request-event runs across PRs: warn (or block under
PR_PREFLIGHT_BLOCK_RED_BASE=1) only when at least 6 decisive runs span 3+
distinct branches and every one of them failed; stay silent otherwise
(some success, too few samples, or an empty/unreadable sample).

Agent-Signature: claude-sonnet-5-medium on behalf of matt wilkie

* fix(pr-preflight): per-workflow PR-gate detection, warn-only, skip on red base

Review found the lumped-sample design could never fire for a single broken
pr.yml-only job: mixing all pull_request-event runs together diluted one red
workflow against every other green PR workflow. Rework to group_by(workflowName)
and flag any single workflow whose decisive runs (>=5, >=3 distinct
headBranch values) are ALL failure-class, naming that workflow in the message.

Also: drop the PR_PREFLIGHT_BLOCK_RED_BASE coupling entirely (a [block] line
here matched neither pr-babysit's red-base nor transient-block patterns, which
would have parked every merge lane and revoked the red-base merge exception —
wiring this into the patrol is a separate change); skip the sample when the
base branch is already red (that already explains PR redness and avoids the
contradictory "base branch shows green" wording); add startup_failure to the
failure-class regex to mirror pr-babysit's probe; and guard against gh exiting
0 with non-JSON output so a malformed sample stays silent instead of aborting
under set -euo pipefail.

Tests reworked for the per-workflow design: fake gh keys the PR-gate dispatch
on the presence of --event pull_request; new cases cover a mixed-workflow
sample (only the all-red workflow fires and is named), red-base-skips-sample,
an unreadable/non-JSON sample, positive coverage of the base-branch dispatch
via BASE_RUN_LIST, and the newest-run selection. Removed the
PR_PREFLIGHT_BLOCK_RED_BASE=1 block-mode test for this detector.

Agent-Signature: claude-sonnet-5-medium on behalf of matt wilkie

* test(pr-preflight): pin the PR-gate detector's warn-only contract; harden jq guard; document

Review round: a regression test asserts the detector never emits [block]
even under PR_PREFLIGHT_BLOCK_RED_BASE=1 (downstream automation classifies
unrecognized block lines as genuine merge blockers), the group_by jq gets an
abort guard for valid-JSON-but-not-objects input, and scripts/README.md
describes the new sample.

Agent-Signature: claude-fable-5-high on behalf of matt wilkie

* fix(pr-preflight): only diagnose the PR gate against a known-green base

Codex review round: with zero decisive base runs preflight has just warned
that base health is undetermined, and the broken-gate message's 'while the
base branch shows green' clause would contradict it. Gate the sample on
base_green_count > 0; fixture cases now supply a green base sample and a new
case pins the undetermined-base silence.

Agent-Signature: claude-fable-5-high on behalf of matt wilkie

* fix(pr-preflight): group the PR-gate sample by workflowDatabaseId, not display name

Codex review round 2: workflow display names are not unique and org/ruleset
runs can lack one entirely, so name-grouping could merge distinct gates.
Group by the stable id, keep the name for display. The repo-wide sample's
base-branch conflation is documented as an accepted limitation of a
warn-only supplemental detector.

Agent-Signature: claude-fable-5-high on behalf of matt wilkie
2026-08-01 22:46:44 -07:00

272 lines
8.4 KiB
Markdown

# Beads Scripts
Utility scripts for maintaining the beads project.
## ci/
Repository-owned CI command wrappers. These scripts are the source of truth for
the target CI tiers; Make targets are aliases for local discoverability.
```bash
make ci-pr-core
make ci-pr-policy
make ci-pr-lint
make ci-package-mcp
make ci-package-npm
```
Each wrapper auto-detects the repository root, sources `.buildflags` when it
invokes Go in the default build mode, and records per-command timing through
`scripts/ci/lib/timing.sh`.
Broad Go test wrappers also source `scripts/ci/lib/test-env.sh`, which creates a
temporary HOME/XDG/Dolt root, isolates Git global/system config, clears runtime
Beads/Dolt environment variables, and sets `BEADS_TEST_SKIP=dolt` before tests
run. This keeps local `make test` and `make ci-pr-core` results comparable to
the fast PR-core contract even on shared agent hosts. Set
`BEADS_TEST_ENV_RUN_DOLT=1` only when intentionally running the Dolt-dependent
tests through these broad wrappers, or `BEADS_TEST_ENV_DISABLE=1` when debugging
against your real local configuration.
The broad Go wrappers also cap package and test parallelism to `4` by default
(`GO_TEST_PKG_PARALLEL` and `GO_TEST_PARALLEL`). This avoids turning high-core
shared hosts into a different test topology than GitHub Actions.
`make ci-pr-policy` includes `scripts/check-testing-short.sh`, which enforces
that `testing.Short()` is only used for runtime, stress, or large-fixture skips.
Use build tags, environment checks, or named wrappers for integration/e2e/API
boundaries.
Package gate wrappers validate publishable/package-adjacent surfaces:
- `make ci-package-mcp` builds or consumes a `bd` binary, puts it on `PATH` as
`bd`, then runs locked MCP package `uv sync`, Ruff, mypy, pytest, and build
checks.
- `make ci-package-npm` builds or consumes the native binary expected by
`npm-package/bin/bd`, runs the npm package test suite, and checks
`npm pack --dry-run`.
Set `BEADS_TEST_BD_BINARY=/path/to/bd` for MCP and npm package gates to reuse a
prebuilt candidate binary instead of rebuilding it inside the wrapper.
## pr-preflight.sh
Read-only PR safety check for agents and maintainers.
```bash
# Before implementing or opening a related PR
./scripts/pr-preflight.sh --search "topic keywords" --repo gastownhall/beads
# Before changing, closing, or merging an existing PR
./scripts/pr-preflight.sh 123 --repo gastownhall/beads
```
It reports contributor/fork status, draft/review/merge/check state, risky diff
signals such as `.beads/` changes or missing tests, and the required
contributor-protection next steps. It does not replace code review or local
validation.
Base-branch health is supplemented by a warn-only PR-gate sample: the last 60
completed `pull_request`-event runs, grouped per workflow. A single workflow
whose decisive runs (>= 5, across >= 3 distinct head branches) are all
failure-class is reported as a broken PR gate — the case where a job that
exists only in the PR workflow is red for every PR while the base branch shows
green. This detector never blocks (deliberately: automation classifies
unrecognized `[block]` lines as genuine merge blockers and would park merge
lanes on a false positive); it is skipped entirely while the base branch is
red.
## gh-body-lint
Lint Markdown files before posting them with `gh ... --body-file`.
```bash
./scripts/gh-body-lint body.md
./scripts/gh-body-lint --fix body.md
```
The lint catches literal `\n` sequences, which render poorly on GitHub, and
`GH#123` references, which do not auto-link like `#123` or `owner/repo#123`.
## release.sh (⭐ The Easy Button)
**One-command release** from version bump to local installation.
### Usage
```bash
# Full release (does everything)
./scripts/release.sh 0.9.3
# Preview what would happen
./scripts/release.sh 0.9.3 --dry-run
```
### What It Does
This master script automates the **entire release process**:
1. ✅ Stops running Dolt servers (avoids version conflicts)
2. ✅ Runs tests and linting
3. ✅ Bumps version in all files
4. ✅ Commits and pushes version bump
5. ✅ Creates and pushes git tag
6. ✅ Verifies or opens the Homebrew core formula PR
7. ✅ Upgrades local Homebrew installation
8. ✅ Verifies everything works
**After this script completes, your system is running the new version!**
### Examples
```bash
# Release version 0.9.3
./scripts/release.sh 0.9.3
# Preview a release (no changes made)
./scripts/release.sh 1.0.0 --dry-run
```
### Prerequisites
- Clean git working directory
- All changes committed
- golangci-lint installed
- Homebrew installed (for local upgrade)
- Push access to gastownhall/beads
### Output
The script provides colorful, step-by-step progress output:
- 🟨 Yellow: Current step
- 🟩 Green: Step completed
- 🟥 Red: Errors
- 🟦 Blue: Section headers
### What Happens Next
After the script finishes:
- GitHub Actions builds binaries for all platforms (~5 minutes)
- PyPI package is published automatically
- Homebrew core formula is verified or tracked through its canonical PR
- Users can `brew upgrade beads` to get the new version after Homebrew merges
- GitHub Release is created with binaries and changelog
---
## bump-version.sh
Bumps the version number across all beads components in a single command.
### Usage
```bash
# Show usage
./scripts/bump-version.sh
# Update versions (shows diff, no commit)
./scripts/bump-version.sh 0.9.3
# Update versions and auto-commit
./scripts/bump-version.sh 0.9.3 --commit
```
### What It Does
Updates version in all these files:
- `cmd/bd/version.go` - bd CLI version constant
- `plugins/beads/.claude-plugin/plugin.json` - Claude plugin version
- `plugins/beads/.codex-plugin/plugin.json` - Codex plugin version
- `.claude-plugin/marketplace.json` - Claude marketplace plugin version
- `integrations/beads-mcp/pyproject.toml` - MCP server version
- `README.md` - Alpha status version
- `PLUGIN.md` - Version requirements
### Features
- **Validates** semantic versioning format (MAJOR.MINOR.PATCH)
- **Verifies** all versions match after update
- **Shows** git diff of changes
- **Auto-commits** with standardized message (optional)
- **Cross-platform** compatible (macOS and Linux)
### Examples
```bash
# Bump to 0.9.3 and review changes
./scripts/bump-version.sh 0.9.3
# Review the diff, then manually commit
# Bump to 1.0.0 and auto-commit
./scripts/bump-version.sh 1.0.0 --commit
git push origin main
```
### Why This Script Exists
Previously, version bumps only updated `cmd/bd/version.go`, leaving other components out of sync. This script ensures all version numbers stay consistent across the project.
### Safety
- Checks for uncommitted changes before proceeding
- Refuses to auto-commit if there are existing uncommitted changes
- Validates version format before making any changes
- Verifies all versions match after update
- Shows diff for review before commit
---
## sign-windows.sh
Signs Windows executables with an Authenticode certificate using osslsigncode.
### Usage
```bash
# Sign a Windows executable
./scripts/sign-windows.sh path/to/bd.exe
# Environment variables required for signing:
export WINDOWS_SIGNING_CERT_PFX_BASE64="<base64-encoded-pfx>"
export WINDOWS_SIGNING_CERT_PASSWORD="<certificate-password>"
```
### What It Does
This script is called automatically by GoReleaser during the release process:
1. **Decodes** the PFX certificate from base64
2. **Signs** the Windows executable using osslsigncode
3. **Timestamps** the signature using DigiCert's RFC3161 server
4. **Replaces** the original binary with the signed version
5. **Verifies** the signature was applied correctly
### Prerequisites
- `osslsigncode` installed (`apt install osslsigncode` or `brew install osslsigncode`)
- EV code signing certificate exported as PFX file
- GitHub secrets configured:
- `WINDOWS_SIGNING_CERT_PFX_BASE64` - base64-encoded PFX file
- `WINDOWS_SIGNING_CERT_PASSWORD` - certificate password
### Graceful Degradation
If the signing secrets are not configured:
- The script prints a warning and exits successfully
- GoReleaser continues without signing
- The release proceeds with unsigned Windows binaries
This allows releases to work before a certificate is acquired.
### Why This Script Exists
Windows code signing helps reduce antivirus false positives that affect Go binaries.
Kaspersky and other AV software commonly flag unsigned Go executables as potentially
malicious due to heuristic detection. See `docs/reference/antivirus.md` for details.
---
## Future Scripts
Additional maintenance scripts may be added here as needed.