From 742046e4f82f06edf8857faa0d58c4dfd252cf77 Mon Sep 17 00:00:00 2001 From: chenkun Date: Mon, 5 Oct 2026 03:27:38 +0800 Subject: [PATCH] docs: close gaps in the PR/release workflow writeup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Release: the v* tag must point at the merged main HEAD — with a squash merge the release branch's local commit is not in main's history, and immutable tags cannot be moved without ruleset surgery. Spell out 'git checkout main && git pull --ff-only'. - Conventions/CONTRIBUTING: no approvals are required to merge (required_approving_review_count: 0, solo project). - Conventions/CONTRIBUTING: write PR titles as conventional-commit subjects — a squash merge makes the title the commit subject that scripts/changelog.ts reads. - Conventions: refresh dependabot PR branches with '@dependabot rebase'; 'gh pr update-branch' fails on workflow-touching PRs without the OAuth workflow scope (bit us 2026-10-04). --- AGENTS.md | 4 ++-- CONTRIBUTING.md | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 552a07c..c02724a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,13 +33,13 @@ Behavioral facts verified against host source; getting these wrong is the histor - Plugin log wording is a **test contract**: the e2e harness and `test/index.test.ts` grep the log for phrases like `startup: N injected`, `reload: N updated/withdrawn`, `reconnecting MCP server(s)` — reword messages and tests together. The log rotates at ~256 KiB keeping one `.old` generation; when debugging a missing line, check both files. - Fix/feat/docs commits must **not** touch `package.json`'s `version` — on `main` it always mirrors the latest published npm version. The bump happens only in the release commit (see Release). Commits up to `v0.4.6` predate this policy (bump-at-commit-time, with `(vX.Y.Z)` suffixes in commit messages), which is why 0.4.5/0.4.6 exist in git history but were never published to npm. - CI (`.github/workflows/ci.yml`): unit tests on ubuntu+windows+macOS × node 22.18/24, e2e matrix os × host. Windows-specific branches (permission checks, `~\` expansion) are why Windows CI exists — don't remove it; macOS shares the POSIX branches with Linux and adds a second Unix userland. It also fires weekly (Mon 06:00 UTC) and on manual dispatch, when each e2e cell additionally runs against the `@latest` host specs — the pinned specs stay sticky, so a red weekly run means a new host release drifted; align the plugin, then bump the pins. All actions across the workflows are SHA-pinned (version in the trailing comment) and every workflow declares least-privilege `permissions:` — keep both when touching CI (OpenSSF Scorecard hygiene); `dependabot.yml` bumps the pins weekly (github-actions only — npm excluded so lockfile resolved hosts stay on npmmirror). `codeql.yml` adds CodeQL SAST (ubuntu, TS, no build). -- **All changes land on `main` via PR** — the `protect main` ruleset rejects direct pushes for everyone (no bypass actors, admins included). A merge requires the `gate` (ci.yml matrix aggregate — one stable context so the ruleset survives matrix changes; keep the job id stable or update the ruleset in the same PR) and `analyze` (CodeQL) checks green on an up-to-date branch. Squash and merge commits are both accepted. +- **All changes land on `main` via PR** — the `protect main` ruleset rejects direct pushes for everyone (no bypass actors, admins included). A merge requires the `gate` (ci.yml matrix aggregate — one stable context so the ruleset survives matrix changes; keep the job id stable or update the ruleset in the same PR) and `analyze` (CodeQL) checks green on an up-to-date branch. Approvals are **not** required (`required_approving_review_count: 0`, solo project) — a green, up-to-date PR is mergeable immediately. Squash and merge commits are both accepted; write the **PR title as a conventional-commit subject** — a squash merge makes the title the landed commit's subject, which is what `scripts/changelog.ts` drafts release notes from. To refresh a dependabot PR branch, comment `@dependabot rebase` — `gh pr update-branch` fails on workflow-touching PRs without the OAuth `workflow` scope. ## Release Releases are **CI-gated** by `.github/workflows/release.yml`: pushing a `v` tag replays the full CI matrix (unit + real-host e2e, ubuntu+windows+macOS — reused from `ci.yml` via `workflow_call`) and only then publishes to npm with provenance and creates the GitHub Release. There is deliberately **no** `NPM_TOKEN` anywhere — npm's trusted-publisher binding only accepts this workflow's OIDC identity, so local `npm publish` fails by design. -- Release flow (**bump-at-release-time**): pick the semver level from the changes accumulated on `main` since the last tag, run `npm version --no-git-tag-version` (bumps `package.json` and syncs `package-lock.json` in one step), commit as `chore: release vX.Y.Z` on a release branch, open a PR and merge it (direct pushes to `main` are rejected — the ruleset has no bypass actors). Then `git pull` on `main` and `v=$(node -p "require('./package.json').version") && git tag -m "v$v" "v$v" && git push origin "v$v"` — the `-m` is not optional on machines with `tag.gpgSign=true` (a bare `git tag` opens an editor and fails non-interactively). The guard job fails the run when tag ≠ package.json version or the version is already on npm. +- Release flow (**bump-at-release-time**): pick the semver level from the changes accumulated on `main` since the last tag, run `npm version --no-git-tag-version` (bumps `package.json` and syncs `package-lock.json` in one step), commit as `chore: release vX.Y.Z` on a release branch, open a PR and merge it (direct pushes to `main` are rejected — the ruleset has no bypass actors). Then `git checkout main && git pull --ff-only` and `v=$(node -p "require('./package.json').version") && git tag -m "v$v" "v$v" && git push origin "v$v"`. **The tag must point at the merged `main` HEAD** — with a squash merge the release branch's local commit is not in `main`'s history, and `v*` tags are immutable, so a tag on the wrong commit cannot be moved without ruleset surgery. The `-m` is not optional on machines with `tag.gpgSign=true` (a bare `git tag` opens an editor and fails non-interactively). The guard job fails the run when tag ≠ package.json version or the version is already on npm. - **Changelog is generated during `npm version`**: the `version` lifecycle script runs `scripts/changelog.ts`, which turns CHANGELOG.md's `[Unreleased]` into a dated section for the new version — curated entries carry over verbatim, conventional commits since the previous tag are appended as draft entries (`chore: release` excluded), link definitions refresh, the file is left staged. **Review/trim the draft before the release commit.** Re-running is a no-op once the section exists. The release guard additionally fails when CHANGELOG.md lacks the `## [X.Y.Z]` section, and the GitHub Release body is extracted from that section (no more `--generate-notes`). - The `npm publish` step must stay directly in `release.yml`: npm validates the *calling* workflow's filename against the trusted-publisher binding — a publish hidden behind `workflow_call` would mismatch (reusing `ci.yml` for the test matrix only is fine). - One-time bootstrap (**done** 2026-10-03 — the binding is live and the v0.4.4 rehearsal published through it; keep for forks/re-creation): trusted publishing can only be configured once the package exists on npm (npm/cli#8544), so the **first** publish is a one-time manual `npm publish` (that one version carries no provenance). Here the package-creating publish was a minimal `0.0.0-stage` placeholder (2 files, `"stub": true`), followed ~1 min later by the real manual `0.4.3`; the stub was **unpublished 2026-10-04** (see next bullet) and no longer exists. Then npmjs.com → package → Settings → Trusted publisher → GitHub Actions: org/user `bytesnail`, repo `opencode-secrets-env`, workflow filename `release.yml` (case-sensitive, `.yml` included; npm does not validate the fields until a publish runs), allowed action `npm publish`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07dcf8e..5e4fafa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,11 @@ enough for most PRs — CI does the rest. `main` is PR-gated for everyone, admins included — direct pushes are rejected by the `protect main` ruleset. A merge needs the required checks green (`gate`, the ci.yml matrix aggregate, plus `analyze` from CodeQL) on -an up-to-date branch; squash and merge commits are both accepted. +an up-to-date branch; no approval is required (solo project). Squash and +merge commits are both accepted — but write the **PR title as a +conventional-commit subject**: a squash merge makes the title the landed +commit's subject, which is what release changelog drafts are generated +from. - One concern per PR; describe the user-visible behavior change. - Update both READMEs when user-facing behavior or options change.