Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>` 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 <patch|minor|major> --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 <patch|minor|major> --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`.
Expand Down
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading