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.