From d38fad967713038b07c712c28194fbf11c06d4df Mon Sep 17 00:00:00 2001 From: AJ Markow Date: Tue, 15 Sep 2026 12:19:06 -0500 Subject: [PATCH] docs: add an agent release runbook and make AGENTS.md canonical Releases were undocumented, so each one relied on rediscovering which files carry the version and which must be left alone. docs/updating-version.md writes that down: the two files that change, the derived files that must not be hand-edited, and why nix/gemset.nix stays on the old version until CI repins it after publish. It also records why the release marker belongs in the pull request title. The master ruleset requires a PR, so the push to master is the squash commit, and GitHub builds that message from the title. A marker in a branch commit does not survive. AGENTS.md becomes the one place repo instructions live, so every coding agent reads the same source. Claude Code reads CLAUDE.md rather than AGENTS.md, so CLAUDE.md now imports it instead of restating the devenv rule and drifting from it. The Backlog.md block is dropped; this repo does not use it. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 56 ++++++++++++++++ CLAUDE.md | 91 ++++++++------------------ docs/updating-version.md | 137 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 63 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/updating-version.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4f84fa6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# Agent instructions + +Facts specific to this repo. General working preferences live in your global config, +not here. + +## Ruby and the dev environment + +This project uses [devenv](https://devenv.sh) (Nix-based) to manage Ruby and the +development environment. Do **not** suggest or use rbenv, rvm, asdf, or Homebrew Ruby. + +```bash +devenv shell # interactive +devenv shell -- # one-off +``` + +The Ruby version is set in `devenv.nix`. It currently targets Ruby 4.0. The gem +itself supports Ruby 3.2 and newer (`required_ruby_version` in the gemspec), and CI +tests that whole range. + +## Releasing a new version + +**Read [`docs/updating-version.md`](docs/updating-version.md) before changing the +version.** Do not improvise a release. + +The three things that trip agents up: + +1. Only `lib/snippet_cli/version.rb` and `CHANGELOG.md` change. The gemspec, the + `version` command, and its spec all read `SnippetCli::VERSION`. +2. `nix/gemset.nix` and `nix/Gemfile.lock` pin the **published** gem. They stay on + the old version during a release. CI repins them after publish. +3. `gem-release-ready` must be in the **pull request title**, not a commit message. + `master` requires a PR, so the squash commit message comes from the PR title, and + that is what triggers `publish`. + +## Nix packaging + +`nix/default.nix` is a `bundlerApp` derivation that installs the published gem from +RubyGems. It does not build from the working tree, so source changes do not appear in +the Nix package until the gem is released and the pins are regenerated. + +The `gum` gem ships its binary in a platform-specific directory. The derivation +patches around this by symlinking `pkgs.gum` into the expected path. If `gum` changes +its layout, that patch in `nix/default.nix` is what breaks. + +Verify packaging changes with `nix flake check --no-build`. Do not run a full build +just to prove the package installs. + +## Tests + +```bash +devenv shell -- bundle exec rake spec +``` + +Some `spec/integration/cli_spec.rb` examples spawn a subprocess through aruba. They +fail in a polluted local Bundler environment and pass in CI. Always compare the +failure count against `master` before assuming a change broke them. diff --git a/CLAUDE.md b/CLAUDE.md index bdc405e..4056333 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,60 +1,13 @@ # Claude Code Instructions -## Ruby Version Management +Claude Code reads this file, not `AGENTS.md`. The repo instructions live in +`AGENTS.md` so every coding agent reads the same source; the import below pulls them +in at session start. Add repo guidance there, not here. -This project uses [devenv](https://devenv.sh) (Nix-based) to manage the Ruby version and development environment. Do **not** suggest or use rbenv, rvm, asdf, or Homebrew Ruby for this project. - -To enter the development environment: - -```bash -devenv shell -``` - -To run a single command inside the devenv environment (without an interactive shell): - -```bash -devenv shell -- -``` - -For example: `devenv shell -- ruby --version` - -The Ruby version is specified in `devenv.nix`. Currently targeting Ruby 4.0. - - - - - -## BACKLOG WORKFLOW INSTRUCTIONS - -This project uses Backlog.md MCP for all task and project management activities. - -### Critical guidance - -- If your client supports MCP resources, read `backlog://workflow/overview` to understand when and how to use Backlog for this project. -- If your client only supports tools or the above request fails, call `backlog.get_backlog_instructions()` to load the tool-oriented overview. Use the `instruction` selector when you need `task-creation`, `task-execution`, or `task-finalization`. - -- **First time working here?** Read the overview resource IMMEDIATELY to learn the workflow -- **Already familiar?** You should have the overview cached ("## Backlog.md Overview (MCP)") -- **When to read it**: BEFORE creating tasks, or when you're unsure whether to track work - -These guides cover: - -- Decision framework for when to create tasks -- Search-first workflow to avoid duplicates -- Links to detailed guides for task creation, execution, and finalization -- MCP tools reference - -You MUST read the overview resource to understand the complete workflow. The information is NOT summarized here. - -### Completing tasks - -`mcp__backlog__task_complete` requires status to be "Done" first. Always call `mcp__backlog__task_edit` with `status: "Done"` before calling `mcp__backlog__task_complete`, or it will error. - - - - +@AGENTS.md + # RTK (Rust Token Killer) - Token-Optimized Commands ## Golden Rule @@ -62,6 +15,7 @@ You MUST read the overview resource to understand the complete workflow. The inf **Always prefix commands with `rtk`**. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use. **Important**: Even in command chains with `&&`, use `rtk`: + ```bash # ❌ Wrong git add . && git commit -m "msg" && git push @@ -73,6 +27,7 @@ rtk git add . && rtk git commit -m "msg" && rtk git push ## RTK Commands by Workflow ### Build & Compile (80-90% savings) + ```bash rtk cargo build # Cargo build output rtk cargo check # Cargo check output @@ -84,6 +39,7 @@ rtk next build # Next.js build with route metrics (87%) ``` ### Test (90-99% savings) + ```bash rtk cargo test # Cargo test failures only (90%) rtk vitest run # Vitest failures only (99.5%) @@ -92,6 +48,7 @@ rtk test # Generic test wrapper - failures only ``` ### Git (59-80% savings) + ```bash rtk git status # Compact status rtk git log # Compact log (works with all git flags) @@ -110,6 +67,7 @@ rtk git worktree # Compact worktree Note: Git passthrough works for ALL subcommands, even those not explicitly listed. ### GitHub (26-87% savings) + ```bash rtk gh pr view # Compact PR view (87%) rtk gh pr checks # Compact PR checks (79%) @@ -119,6 +77,7 @@ rtk gh api # Compact API responses (26%) ``` ### JavaScript/TypeScript Tooling (70-90% savings) + ```bash rtk pnpm list # Compact dependency tree (70%) rtk pnpm outdated # Compact outdated packages (80%) @@ -129,6 +88,7 @@ rtk prisma # Prisma without ASCII art (88%) ``` ### Files & Search (60-75% savings) + ```bash rtk ls # Tree format, compact (65%) rtk read # Code reading with filtering (60%) @@ -137,6 +97,7 @@ rtk find # Find grouped by directory (70%) ``` ### Analysis & Debug (70-90% savings) + ```bash rtk err # Filter errors only from any command rtk log # Deduplicated logs with counts @@ -148,6 +109,7 @@ rtk diff # Ultra-compact diffs ``` ### Infrastructure (85% savings) + ```bash rtk docker ps # Compact container list rtk docker images # Compact image list @@ -157,12 +119,14 @@ rtk kubectl logs # Deduplicated pod logs ``` ### Network (65-70% savings) + ```bash rtk curl # Compact HTTP responses (70%) rtk wget # Compact download output (65%) ``` ### Meta Commands + ```bash rtk gain # View token savings statistics rtk gain --history # View command history with savings @@ -174,16 +138,17 @@ rtk init --global # Add RTK to ~/.claude/CLAUDE.md ## Token Savings Overview -| Category | Commands | Typical Savings | -|----------|----------|-----------------| -| Tests | vitest, playwright, cargo test | 90-99% | -| Build | next, tsc, lint, prettier | 70-87% | -| Git | status, log, diff, add, commit | 59-80% | -| GitHub | gh pr, gh run, gh issue | 26-87% | -| Package Managers | pnpm, npm, npx | 70-90% | -| Files | ls, read, grep, find | 60-75% | -| Infrastructure | docker, kubectl | 85% | -| Network | curl, wget | 65-70% | +| Category | Commands | Typical Savings | +| ---------------- | ------------------------------ | --------------- | +| Tests | vitest, playwright, cargo test | 90-99% | +| Build | next, tsc, lint, prettier | 70-87% | +| Git | status, log, diff, add, commit | 59-80% | +| GitHub | gh pr, gh run, gh issue | 26-87% | +| Package Managers | pnpm, npm, npx | 70-90% | +| Files | ls, read, grep, find | 60-75% | +| Infrastructure | docker, kubectl | 85% | +| Network | curl, wget | 65-70% | Overall average: **60-90% token reduction** on common development operations. - \ No newline at end of file + + diff --git a/docs/updating-version.md b/docs/updating-version.md new file mode 100644 index 0000000..bdf084b --- /dev/null +++ b/docs/updating-version.md @@ -0,0 +1,137 @@ +# Updating the version + +How to release a new version of snippet_cli. Follow these steps in order. + +## Change exactly two files + +| File | Change | +| ---------------------------- | ------------------------------------------------------------------ | +| `lib/snippet_cli/version.rb` | Set `VERSION` to the new number. This is the only source of truth. | +| `CHANGELOG.md` | Add a section for the new version. | + +That is the whole edit. Do not change the version anywhere else. + +## Never hand-edit these + +These already derive the version. Editing them creates two sources of truth. + +- `snippet_cli.gemspec` — reads `SnippetCli::VERSION` +- `lib/snippet_cli/commands/version.rb` — interpolates the constant +- `spec/commands/version_spec.rb` — asserts against the constant + +These two are different. They pin the **published** gem, not the source: + +- `nix/gemset.nix` +- `nix/Gemfile.lock` + +They must stay on the **old** version while you release. The new gem does not exist +on RubyGems yet, so bundix cannot resolve it. CI repins them after publish. If you +bump them by hand, the build breaks. + +## Choose the number + +Follow [semantic versioning](https://semver.org). One case is easy to get wrong: + +**Raising `required_ruby_version` is a minor bump, not a patch.** It stops users on +the dropped Ruby from installing at all. Treat any narrowing of supported Ruby, +Espanso schema, or platform the same way. + +## Steps + +1. Branch off `master`. Name it `release/`. +2. Set `VERSION` in `lib/snippet_cli/version.rb`. +3. Add a `CHANGELOG.md` section. Write it for users, not for the git log. Read the + commits since the last tag to find what actually changed: + ```bash + git log --no-merges --pretty="%s" v..master + ``` +4. Run the suite: + ```bash + devenv shell -- bundle exec rake spec + ``` + Compare the failure count against `master` before you assume you broke something. + Some `spec/integration/cli_spec.rb` examples fail in a polluted local Bundler + environment and pass in CI. +5. Commit and push the branch. +6. Open a PR against `master`. **Put `gem-release-ready` in the PR title.** +7. Wait for the required checks. +8. Squash-merge. Keep `gem-release-ready` in the squash commit message. + +## Why the marker goes in the title + +The `publish` job runs only when this is true: + +```yaml +github.event_name == 'push' && github.ref == 'refs/heads/master' +&& contains(github.event.head_commit.message, 'gem-release-ready') +``` + +The `master` ruleset requires a pull request, so the push to `master` is the squash +commit. GitHub builds that commit message from the PR title. A marker in a branch +commit does not survive the squash. **No marker means no release.** + +## What CI does + +| Job | When | Purpose | +| --------------- | ------------------ | -------------------------------------------------------------- | +| `test` | every push and PR | rspec across Ruby 3.2–4.0 on Linux and macOS | +| `package` | every push and PR | installs the built gem and runs it outside the checkout | +| `gemset-check` | every push and PR | compares the Nix pins to `version.rb` | +| `publish` | marker on `master` | builds, pushes to RubyGems and GitHub Packages, tags, releases | +| `update-gemset` | after `publish` | repins the Nix files and opens a PR | + +## Expect `gemset-check` to pass with a notice + +On a release PR, `version.rb` is ahead of `nix/gemset.nix`. That is correct, not a +failure. The job asks RubyGems whether the new version is published. It is not yet, +so the job treats it as a release in flight and passes: + +``` +::notice::version.rb is , which RubyGems has not indexed yet. +Release in flight -- update-gemset repins after publish. +``` + +If it **fails** instead, the pins lag a version that is already published. That is +real drift. Regenerate them (see below). + +## After the merge + +`publish` pushes the gem, then `update-gemset` runs. It waits for RubyGems to index +the release, regenerates the Nix pins, and opens a `chore/repin-gemset-v` +PR with auto-merge enabled. Confirm that PR lands. + +Then verify: + +```bash +gem list -r snippet_cli # new version is on RubyGems +nix run github:ajmarkow/snippet_cli -- version +``` + +## Failure modes + +**`update-gemset` times out.** It polls RubyGems for 10 minutes. If indexing is +slower, the job fails but the gem is already published. Rerun that job alone from +the Actions tab. Nothing is broken. + +**Bundler resolves the wrong version.** `nix/Gemfile` pins `~> 0.5`, which stops +before 1.0. A major release falls outside it. The job detects this and fails rather +than repinning the wrong version. Widen the constraint in `nix/Gemfile`. + +**`update-gemset` fails at checkout.** The `GEMSET_BOT_TOKEN` secret is missing or +expired. It must be a fine-grained PAT with Contents and Pull requests read/write. +`GITHUB_TOKEN` does not work here: GitHub does not start check runs for pull +requests it opens, so the required checks never report and auto-merge waits forever. + +## Regenerating the Nix pins by hand + +Only do this when `gemset-check` reports real drift, or when `update-gemset` failed +and you are cleaning up. The gem must already be on RubyGems. + +```bash +nix develop --command bash -c "cd nix && bundle lock --update snippet_cli && bundix" +nix fmt nix/gemset.nix +``` + +Both lines matter. `--update snippet_cli` keeps unrelated transitive gems pinned. +`nix fmt` matches the checked-in formatting, because bundix emits compact lists. +Run together, they produce no drift when the pins are already correct.