AGENTS.md · diff

git:20260929.3d5e233 to git:20261006.3a57245

16 added, 27 removed. Audit A to A.

# AGENTS.md — working on the mutineer gem
Conventions for any agent or contributor changing this repo. (Usage docs for
*consumers* live in `README.md` and `docs/`; this file is about developing the gem.)
## Non-negotiables
- **Zero runtime dependencies — Prism + stdlib only.** Never add a runtime gem.
Dev-only deps (minitest, rake, yard) are fine. Ruby **≥ 3.4** (Prism ships with it).
- **No `eval`.** Removed in 0.6.1 (supply-chain scanner flag). The redefine strategy
`load`s a tempfile snippet instead — don't reintroduce `eval`.
- **Least astonishment.** Names match behaviour, no hidden side effects, explicit
failures over silent ones. Match the surrounding code's idiom.
## Before every commit (local gate)
```sh
bundle exec rake test # zero-dep suite
ruby -Ilib -e 'require "mutineer"' # load smoke
bundle exec rake yard:strict # 100% documented — see below
```
The default `Gemfile` pins Minitest 5. For changes to the Minitest
integration, also run the suite on Minitest 6:
```sh
BUNDLE_GEMFILE=gemfiles/minitest6.gemfile bundle install
BUNDLE_GEMFILE=gemfiles/minitest6.gemfile bundle exec rake test
```
- **`yard:strict` requires 100% documentation, including private methods AND
constants.** Every new method and constant needs a YARD docstring / `#` comment
or CI fails.
- **Rails-dependent tests run via `bundle exec rake test:daemon`**, never the
zero-dep default. The gem's own suite stays Rails-free; `test/fixtures/rails_app`
has its OWN bundle. `rake test:daemon` runs in the rails-integration CI job.
- Subprocesses use plain `bundle exec` — never hardcode `rbenv exec` (breaks CI and
non-rbenv users).
## Website checks
For changes to the GitHub Pages site or its tests, also run:
```sh
bundle exec rake site:build
node --test test/site_test.js
npm ci --prefix test/browser
npx --prefix test/browser playwright install chromium
npm test --prefix test/browser
```
`rake site:build` writes the published tree to `_site/` — the YARD API docs,
`sitemap.xml`, `llms-full.txt`, and `json-schema.html` are build artifacts, not
committed files under `docs/`. The checks above read `_site/`, so build first.
These development-only checks require Node.js 22+, npm, and Python 3.
Playwright starts a local server on port 8766; keep that port free so the
checks run against this checkout. The gem and its default Rake suite remain
independent of Node and Playwright. CI runs these checks in `website browser tests`.
## CI gates that block merge
`yard:strict` · `test` (×2 OS) · `test (minitest 6)` · `rails dogfood` + daemon integration · `website browser tests` · socket/gitguardian.
- ## PR review gate (before any merge — never skip)
-
- Applies to **every** open PR (including stacks). Do not merge and do not
- suggest merge until this is done on the current head.
+ ## PR review gate (before any merge)
- ### Before opening a new PR
+ Applies to every open pull request, including a stack. Do not merge until
+ the current head meets this bar.
- Run `/dt-ship-pre-pr-gate` on the exact PR-head commit:
+ ### Before opening a new pull request
- 1. `/ce-code-review` — fix findings
- 2. `/ie-review` — fix findings
- 3. `/cubic-loop` (local) — fix findings
+ Run the checks in "Before every commit". For a site change, run the website
+ checks too. Open the pull request when those checks pass.
- Then `gh pr create`. A gate run is stale after any later commit; re-run.
+ After a later commit, run the checks that the new lines affect.
- ### After the PR is open
+ ### After the pull request is open
- 1. **Re-review the PR** with the same three lenses (CE, IE, cubic PR/local
- mode). Fix real findings; commit and push.
- 2. **Address every review comment/thread** (cubic, bots, humans):
- - Fix or document why not
- - **Reply inline on every thread** (fix + commit SHA, or rationale)
- - Never resolve silently
- 3. **CI green** on the head SHA
- 4. Readiness skills only after 1–3: `check-pr-comments`,
- `dt-ship-pr-readiness`. Human merges.
+ 1. Read every review comment. Fix a real finding, or write why the comment
+ is wrong.
+ 2. Reply on every thread before resolving it. A fix reply names the commit.
+ A decline reply gives the reason.
+ 3. GitHub checks are green on the head commit.
+ 4. A person merges.
- **Stacked PRs:** gate each PR from the bottom of the stack up. After fixing
- a lower PR, restack dependents and re-gate them.
+ **Stacked pull requests:** start at the bottom of the stack. After a lower
+ pull request changes, update the ones above it and review the new lines.
## Releasing (tag-driven — CI publishes, no manual `gem push`)
Semver: **new feature → minor bump, fix → patch.** A behavior change to the
GitHub Action's defaults is a **major** bump: consumers pin `@v<major>`, and a
default flip must never reach them without opting in.
1. On a branch, bump `lib/mutineer/version.rb` and move the `CHANGELOG.md`
`## [Unreleased]` block into a dated `## [X.Y.Z] - YYYY-MM-DD` section.
2. Merge to `main`.
3. Tag and push — this is the whole release trigger:
```sh
git tag vX.Y.Z && git push origin vX.Y.Z
```
4. `.github/workflows/release.yml` then: guards `tag == Mutineer::VERSION`, runs
tests, publishes to RubyGems via **Trusted Publishing** (OIDC — no API key, no
OTP), cuts the GitHub release from the CHANGELOG section, and force-moves the
floating major tag (`v1`, `v2`, …) to the release so `uses: …@v<major>`
consumers track the latest in that line.
Safety nets:
- The **tag must equal `Mutineer::VERSION`** or the release aborts.
- **Releases are batched, not cut per merge.** `.github/workflows/release-pr.yml` runs
weekly (Monday 08:00 UTC) and on demand (`gh workflow run release-pr.yml`). When
`feat:`/`fix:` commits sit on `main` past the latest tag, it opens a release PR (bumps
`version.rb`, dates the CHANGELOG + adds its reference-link def). Each run rebuilds that
PR from the current `main`, so it always covers every change since the last tag; a
newer version supersedes an older open release PR, and when `main` moved the same
version is re-opened fresh (the old branch is deleted under a lease, never
force-pushed). The run never replaces a release branch that holds someone else's
commits, or that has a PR it did not open (it leaves that branch alone with a
warning), and it never closes a release PR it did not open. A merge commit counts as
someone else's commit: GitHub's "Update branch" button on the release PR pauses the
automation until that PR is merged or its branch is deleted. Review + merge it, then push the
`vX.Y.Z` tag. Every release moves the floating major tag (`v1` today), so Action users get it at once:
batch changes rather than releasing after each merge. (To get CI on that auto-PR, add a
`RELEASE_PR_TOKEN` PAT secret — a PR opened by the default `GITHUB_TOKEN` doesn't
trigger other workflows.)
- **The release-PR run stops (and opens nothing) in two cases.** If `VERSION` on `main`
differs from the latest tag, a bump is merged but not tagged: it warns you to push
that tag first. If the computed `vX.Y.Z` tag already exists on origin (for example
pushed from a branch), it fails and you resolve it by hand.
- **GitHub disables scheduled workflows in a public repo after 60 days without
activity.** If no release PR appears on a Monday while unreleased `feat:`/`fix:`
work sits on `main`, check `gh workflow view release-pr.yml` and re-enable it with
`gh workflow enable release-pr.yml`.
**One-time setup (required for the publish to work):** register a Trusted Publisher
on <https://rubygems.org/gems/mutineer> → owner `davidteren`, repo `mutineer`,
workflow `release.yml`, no environment. Without it, `release.yml`'s `gem push` fails.
## Score-model discipline (don't regress this)
`score = killed / (killed + survived)`. `no_coverage`, `uncapturable`, `ignored`,
`skipped`, `errored` are ALL excluded from the denominator. Empty denominator → `nil`,
never `0.0`. The exact-survivor integration oracle must stay green.
-
- ## Repo mechanics
-
- - `docs/plans/` is gitignored by a global rule — plan docs need `git add -f`.
- - A git hook auto-branches commits made directly on `main`; commit on a feature branch.